目录
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