Spring Boot 静态资源处理(六)

1 Servlet 方式相同

Spring MVC 的入口是 DispatcherServlet ,所有的请求都会汇集于该类,而后分发给不同的处理类。如果不做额外的配置,是无法访问静态资源的。
在这里插入图片描述
如果想让 Dispatcher Servlet 直接可以访问到静态资源,最简单的方法当然是交给默认的 Servlet 。
在这里插入图片描述

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }
}

这种情况下 Spring MVC 对资源的处理与 Servlet 方式相同。

2 Spring MVC 方式

我们可以通过很简单的配置使得 Spring MVC 有能力处理对静态资源进行处理。

在 Spring MVC 中,资源的查找、处理使用的是责任链设计模式( Filter Chain )
在这里插入图片描述

其思路为如果当前 resolver 找不到资源,则转交给下一个 resolver 处理。 当前 resolver 找到资源则立即返回给上级 resovler (如果存在),此时上级 resolver 又可以选择对资源进一步处理或再次返回给它的上级(如果存在)。

配置方法为重写 WebMvcConfigurerAdapter 类的 addResourceHandlers 。

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
    @Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
    registry.addResourceHandler("/webjars/**")
                .addResourceLocations(
                        "classpath:/META-INF/resources/webjars/");
}

在这里插入图片描述

该 resolver 的作用是将 url 为 /webjars/** 的请求映射到 classpath:/META-INF/resources/webjars/。

比如请求 http://localhost:8080/webjars/jquery/3.1.0/jquery.js 时, Spring MVC 会查找路径为 classpath:/META-INF/resources/webjars/jquery/3.1.0/jquery.js 的资源文件。

2.1 为静态资源添加版本号

为了简单起见,我们假设静态资源存放在 classpath:/static,且映射的 url 为 /static。

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {

    // 映射 /static 的请求到 classpath 下的 static 目录

    registry.addResourceHandler("/static/**")
                .addResourceLocations("classpath:/static");
    }
}

比如,请求 /static/style.css, 则会直接查找 classpath:/static/style.css。

我们刚才说到,这段代码实际上是添加了一个 PathResourceResolver ,来完成对资源的查找,那么我们是不是可以继续向 Resolver Chain 添加更多的 Resource Resolver ,从而实现对静态资源更多样化的处理呢?

答案是肯定的,接下来,我们添加 VersionResourceResolver 。
在这里插入图片描述

VersionResourceResolver 可以为资源添加版本号。其所作的工作如下:首先使用下一个 resolver 获取资源,如果找到资源则返回,不做其它处理;如果 下一个 resolver 找不到资源,则尝试去掉 url 中的 version 信息,重新调用下一个 resolver 处理,然后无论下一个 resolver 能否处理,都返回其结果。

版本号的策略有两种,下面分别阐述。

2.1.1 指定版本号

指定固定值作为版本号,比如:

@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
   registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static")
           // resourceChain(false) 的作用后面会讲解
           .resourceChain(false)
           // 添加 VersionResourceResolver ,且指定版本号
           .addResolver(new VersionResourceResolver()
               .addFixedVersionStrategy("1.0.0", "/**"));
}

这样,在请求资源时,加上 /1.0.0 前缀,即 http://localhost:8080/static/1.0.0/style.css 也可正确访问。

VersionResourceResolver 在处理该请求时,首先使用 PathResourceResolver 按照配置的映射关系 “/static/**” => “classpath:/static” 处理,即查找文件 classpath:/static/1.0.0/style.css。由于该文件不存在, VersionResourceResolver 尝试去掉版本号 1.0.0 ,然后再次查找 classpath:/static/style.css,找到文件,直接返回。

2.1.2 使用 MD5 作为版本号

除了指定版本号,也可以使用资源的 MD5 作为其版本号,配置方法为:

@Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/static/**")
                .addResourceLocations("classpath:/static/")
                .resourceChain(false)
                .addResolver(new VersionResourceResolver()
                    .addContentVersionStrategy("/**"));
    }

这样,请求资源时,加上资源的 md5 ,即 http://localhost:8080/static/style-dfbe630979d120fe54a50593f2621225.css 也可正确访问。

由于使用资源的 MD5 作为版本号,是 VersionResourceResolver 的其中一种策略,因此与指定版本号的处理方式相同,不再阐述。

2.2 gzip 压缩

很多时候,为了降低传输的数据量,可以对资源进行压缩。比如可以将 style.css 压缩成 style.css.gz ,但是如何让 Spring MVC 在处理对 style.css 的请求时能正确返回 style.css.gz 呢?

为了解决这个问题,我们可以继续添加一个 Resource Resolver —— GzipResourceResolver 。
在这里插入图片描述

GzipResourceResolver 用来查找资源的压缩版本,它首先使用下一个 Resource Resolver 查找资源,如果可以找到,则再尝试查找该资源的 gzip 版本。如果存在 gzip 版本则返回 gzip 版本的资源,否则返回非 gzip 版本的资源。

比如对于如下的资源:

static
    └─ style.css
    └─ style.css.gz (使用 gzip 压缩)

在请求 /static/style.css 时,会先使用 PathResourceResolver 查找 style.css ,找到后则再次查找 style.css.gz 。这里该文件是存在的,因此会返回 style.css.gz 的内容。

PS : 请求头中的 Content-Encoding 要包含 gzip

配置 GzipResourceResolver 很简单:

@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
   registry.addResourceHandler("/static/**")
           .addResourceLocations("classpath:/static/")
           .resourceChain(false)
           .addResolver(new GzipResourceResolver())
           .addResolver(new VersionResourceResolver().addContentVersionStrategy("/**"));
           
}

3.3 chain cache

从上面的情况可以看出, Spring MVC 会对资源进行较多的处理。如果每一次请求都做这些处理,无疑会降低服务器的性能。为了避免这种情况,这时可以添加 CachingResourceResolver 来解决这种问题。

在这里插入图片描述

CachingResourceResolver 用于缓存其它 Resource Resolver 查找到的资源。因此 CachingResourceResolver 会被放在最外层。请求先到达 CachingResourceResolver ,尝试在缓存中查找,如果找到,则直接返回,如果找不到,则依次调用后面的 resolver ,直到有一个 resolver 能够找到资源, CachingResourceResolver 将找到的资源缓存起来,下次请求同样的资源时,就可以从缓存中取了。

可能有人会担心缓存资源会占用太多的内存。但实际上并没有资源内容,仅仅是对资源的路径(或者说资源的抽象)进行了缓存。

开启缓存的方法很简单:

.requestChain(true)

前面的例子中都选择关闭 chain cache ,原因是缓存的存在会增加调试的难度。因此开发时可以考虑关闭该功能。

2.4 省略 webjar 版本

AbstractResourceResolver 的子类一共有 5 个,我们已经提到了 4 个。最后一个是 WebJarsResourceResolver 。
在这里插入图片描述

WebJarsResourceResolver 并不需要手动添加。 WebJarsResourceResolver 依赖了 webjars-locator 包,因此当添加了 webjars-locator 依赖时, Spring MVC 会自动添加 WebJarsResourceResolver 。

<dependency>
    <groupId>org.webjars</groupId>
    <artifactId>webjars-locator</artifactId>
    <version>0.32</version>
</dependency>

WebJarsResourceResolver 的作用是可以省略 webjar 的版本。比如对于请求 http://localhost:8080/webjars/jquery/3.1.0/jquery.js 省略版本号 3.1.10 直接使用
http://localhost:8080/webjars/jquery/jquery.js 也可访问。

至此所有 Spring MVC 提供的 ResourceResolver 都讲完了。 Spring MVC 提供的这 4 个 ResourceResolver 基本够用,如果不能满足业务需求,也可以自定义 ResourceResolver 来满足需求。

3.5 Transformer
实际上,除了 ResourceResolver , Spring MVC 还支持修改资源内容,即 Resource Transformer 。

在这里插入图片描述

可用的 Resource Transformer 有以下几个:

在这里插入图片描述
他们的功能依次为:

AppCacheManifestTransformer: 帮助处理 HTML5 离线应用的 AppCache 清单内的文件
CachingResourceTransformer: 缓存其它 transfomer 的结果,作用同 CachingResourceResolver
CssLinkResourceTransformer: 处理 css 文件中的链接,为其加上版本号
ResourceTransformerSupport: 抽象类,自定义 transfomer 时继承
我们拿 CssLinkResourceTransformer 举例。 它会将 css 文件中的 @import 或 url() 函数中的资源路径自动转换为包含版本号的路径。

配置方法为:

registry.addResourceHandler("/static/**")
                .addResourceLocations("classpath:/static/")
                .resourceChain(false)
                .addResolver(new VersionResourceResolver().addContentVersionStrategy("/**"))
                .addTransformer(new CssLinkResourceTransformer());

当我们在 style.css 中通过 @import “style-other.css”; 导入了另一个 css 文件,则 transformer 会自动将该 style.css 内部的 css 文件路径地址转换为: @import “style-other-d41d8cd98f00b204e9800998ecf8427e.css”

3.6 Http 缓存
为了避免客户端重复获取资源,HTTP/1.1 规范中定义了 Cache-Control 头。几乎所有浏览器都实现了支持 Cache-Control。

配置方法如下:

@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
   registry.addResourceHandler("/static/**")
           .addResourceLocations("classpath:/static/")
           .setCacheControl(CacheControl
                   .maxAge(10, TimeUnit.MINUTES)
                   .cachePrivate());
}

当请求 /static/style.css 时,返回的头信息中会多两条信息:

Cache-Control:max-age=600, private
Last-Modified:Sun, 04 Oct 2016 15:08:22 GMT

浏览器会将该信息连同资源储存起来,当再次请求该资源时,会取出 Last-Modified 并添加到在请求头 If-Modified-Since 中:

If-Modified-Since:Sun, 04 Oct 2016 15:08:22 GMT

Spring MVC 在收到请求,发现存在 If-Modified-Since,会提取出来该值,并与资源的修改时间比较,如果发现没有改变,则仅仅返回状态码 304 ,无需传递资源内容。浏览器收到状态码 304 ,明白资源从上次请求到现在未被改变, http 缓存依旧可用。

3 配置文件配置

我们使用 spring boot 提供的编写配置文件的方式,实现上面使用代码才能完成的功能。

# application.properties

# 设置静态资源的存放地址
spring.resources.static-locations=classpath:/resources 

# 开启 chain cache
spring.resources.chain.cache=true

# 开启 gzip
spring.resources.chain.gzipped=true

# 指定版本号
spring.resources.chain.strategy.fixed.enabled=true
spring.resources.chain.strategy.fixed.paths=/static  
spring.resources.chain.strategy.fixed.version=1.0.0

# 使用 MD5 作为版本号
spring.resources.chain.strategy.content.enable=true
spring.resources.chain.strategy.content.paths=/**

# http 缓存过期时间
spring.resources.cachePeriod=60 

猜你喜欢

转载自blog.csdn.net/u010811939/article/details/86300048