从零搭建开发脚手架 静态资源、自定义扩展、缓存静态资源

274 阅读7分钟

文章目录

现在的绝大部分架构都是Nginx作为前端静态资源服务器,其实如果我们应用用户体量不大的话,可以直接使用Spring Boot 来做静态资源服务器与后台接口服务一起发布,降低运维部署成本。

简单入门

Spring Boot内置了ResourceHttpRequestHandler.java的预配置实现, 以方便提供静态资源。

默认情况下,从classpath上的/static/public/resources/META-INF/resources 目录中的任何一个提供静态内容

相关代码如下:ResourceProperties.java

	private static final String[] CLASSPATH_RESOURCE_LOCATIONS = { "classpath:/META-INF/resources/",
			"classpath:/resources/", "classpath:/static/", "classpath:/public/" };

例如,如果我们在类路径的/static目录中放置index.html文件 ,则可以通过http://localhost:8080/index.html访问该文件 。我们也可以通过将文件添加到其他上面提到的目录中来获得相同的结果。

自定义请求路径

默认情况下,Spring Boot在请求的根部分(即/**)下提供所有静态内容

我们也可以通过spring.mvc.static-path-pattern来更改它。

例如,如果我们想通过http://localhost:8080/laker/index.html访问相同的文件,则可以在application.yaml中配置:

spring:
  mvc:
    static-path-pattern: /laker/**

自定义文件目录

上面已经介绍了默认的文件路径,也可以通过spring.resources.static-locations 配置属性更改默认资源位置。此属性可以接受多个资源位置

spring:
  resources:
    static-locations: [classpath:/files/,file:/opt/files]

classpath: 类路径,file: 本地磁盘文件。
注意: 这样配置会覆盖默认的文件目录,如果你引入了swagger,那么你的swagger页面就无法访问了。

自定义请求路径+文件目录

上面2种方式都是全局的配置,不够灵活,通过WebMvcConfigurer.addResourceHandlers可以灵活扩展更多自定义配置。

@Configuration
public class MvcConfig implements WebMvcConfigurer {
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry
          .addResourceHandler("/laker1/**")
          .addResourceLocations("classpath:/laker1/","file:/opt/files/");// 多个位置	
    }
}

这里是扩展请求路径和文件目录,不会覆盖默认的配置。

缓存静态资源

为了提高效率,可以将一些静态的资源缓存起来。

@Configuration
public class MvcConfig implements WebMvcConfigurer {
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/laker1/**") 
                .addResourceLocations("classpath:/static/") 
                .setCacheControl(CacheControl.maxAge(1, TimeUnit.DAYS));
    }
}

上面的代码实际是将响应头的Cache-Control设置为Cache-Control: max-age=86400,浏览器将使用1天的缓存文件。

客户端第一次访问时

客户端第一次访问时,它将通过网络接收整个文件,其状态码为200 OK。该响应将具有以下标头来控制缓存行为:

Cache-Control: max-age=86400

客户端第二次访问时

当客户端第二次请求相同文件时,浏览器将不会再向服务器发出另一个请求。取而代之的是,它将直接从其缓存中提供文件避免网络请求,因此页面加载速度会更快。

注意:我用的Chrome浏览器,一定要新建一个tab去测试第二次请求,否则会默认不使用缓存Cache-Control,而使用Last-modified返回304。

缓存协议 Expires、Cache-Control、Last-modified 、Etag的区别

浏览器缓存策略分为两种,强制缓存和协商缓存:
强制缓存
用户发送的请求,直接从客户端缓存中获取,不发送请求到服务器,不与服务器发生交互行为

  • Expires
  • Cache-Control

协商缓存
用户发送的请求,发送到服务器后,由服务器判定是否从缓存中获取资源。

  • Last-modified
  • Etag

强制缓存在浏览器缓存位置分为四种,其优先级顺序如下:

  • Service Worker
  • Memory Cache
  • Disk Cache
  • Push Cache

当上述四个缓存位置中的缓存都没有命中时,则会向服务器发起请求。
Service Worker: 不做了解;
Push Cache: 不做了解;
Memory Cache: 即内存中的缓存,其特点是容量小、读取高效、持续性短,会随着进程的释放而释放,在内存使用率低、缓存小尺寸资源时,会以 Memory Cache 为优先;
Disk Cache: 即磁盘中的缓存,其特点是容量大、读取缓慢、持续性长,任何资源都能存储到磁盘中,在内存使用率高、缓存大尺寸资源时,会以 Disk Cache 为优先;

Expires

Expires 是 HTTP 1.0 的字段,而 Cache-Control 是 HTTP 1.1 的字段,当 Expires 与 Cache-Control 同时存在时,Cache-Control 的优先级要高于 Expires。为了保证浏览器兼容,一般两个字段后端都会同时返回给前端,若是命中缓存(即存在缓存资源并且缓存资源未过期),则浏览器响应 HTTP Status Code 200,并直接使用缓存资源作为返回结果,不需要发起 HTTP 请求,为强制缓存;若是存在缓存资源但缓存资源已过期,则进入下一步骤协商缓存。

Cache-Control

Cache-Control 是指缓存指令,这个指令控制谁在什么条件下可以缓存响应以及可以缓存多久。这个协定取代了以前的 Expires 指令,在 HTTP/1.1 开始支持,在这么长时间后,我们可以认为 Cache-Control 在正常环境下都是支持的。

缓存请求指令

客户端可以在HTTP请求中使用的标准 Cache-Control 指令。

Cache-Control: max-age=<seconds>
Cache-Control: max-stale[=<seconds>]
Cache-Control: min-fresh=<seconds>
Cache-control: no-cache
Cache-control: no-store
Cache-control: no-transform
Cache-control: only-if-cached

缓存响应指令

服务器可以在响应中使用的标准 Cache-Control 指令。

Cache-control: must-revalidate
Cache-control: no-cache
Cache-control: no-store
Cache-control: no-transform
Cache-control: public
Cache-control: private
Cache-control: proxy-revalidate
Cache-Control: max-age=<seconds>
Cache-control: s-maxage=<seconds>

详细内容可参见MDN developer.mozilla.org/zh-CN/docs/…

Last-modified

Last-Modified 是由服务端返回的,用于告知客户端最后一次修改是什么时候。客户端需要记录下来这个值,并在下一次请求的时候,通过 If-Modified-Since 这个字段附上上一次服务端返回的 Last-Modified 的值。在这种情况下,服务端就有了两次时间,在通过比对后,就可以知道在这段时间内,内容是否发生了改变。如果没有发生变化,就会返回 304 NOT MODIFIED 这个状态码,而不是通常的 200。反之如果发生了变化,就进行正常的返回。

Etag

如果资源本身确实会随时发生改动,还用 Cache-Control 就会使用户看到的页面得不到更新。

服务端会返回相应的 Etag,这个 Etag 客户端不用关心其具体是怎么实现的,只需要能够记录下这个值就行。服务端是通过对内容进行 hash,或者别的算法来生成这样的 Etag,当客户端请求的时候,只需要去检查两者是否相同,即可知道内容有没有发生变化。返回的方式,与前面 If-Modified-Since and Last-Modified 相同

缓存流程

不同刷新的请求执行过程

  1. 浏览器地址栏中写入URL,回车
    浏览器发现缓存中有这个文件了,不用继续请求了,直接去缓存拿。(最快)
  2. F5
    F5就是告诉浏览器,别偷懒,好歹去服务器看看这个文件是否有过期了。于是浏览器就胆胆襟襟的发送一个请求带上If-Modify-since。
  3. Ctrl+F5
    告诉浏览器,你先把你缓存中的这个文件给我删了,然后再去服务器请求个完整的资源文件下来。于是客户端就完成了强行更新的操作.

浏览器的缓存机制:

图片来自:www.jianshu.com/p/fd00f0d02…

参考

🍎QQ群【837324215】
🍎关注我的公众号【Java大厂面试官】,一起学习呗🍎🍎🍎