前一段时间,WebIDE 开源的过程当中,无心间接触到 webjars,以为比较有趣,因而研究并整理了一下。css
webjars 是将前端的库(好比 jQuery)打包成 Jar 文件,而后使用基于 JVM 的包管理器(好比 Maven、Gradle 等)管理前端依赖的方案。html
webjars 的效果很是神奇。对于其用法,咱们能够在 maven 项目中添加下面的依赖:前端
<dependency> <groupId>org.webjars</groupId> <artifactId>jquery</artifactId> <version>3.1.0</version> </dependency>
而后经过请求 http://localhost:8080/webjars/jquery/3.1.0/jquery.js
便可正确访问到 jquery 文件。java
能够再举一个应用场景的例子,好比项目要添加 Api 文档,决定使用 Swagger,demo 参见。效果如图:jquery
该框架有两部分,一部分是 springfox-swagger2 提供后端实现,另外一部分是 springfox-swagger-ui 提供前端实现。引入后端实现很简单,加入 maven 依赖便可,可是引入 springfox-swagger-ui 麻烦一些。git
一种方式是将该项目编译后的 source 加入到项目。这种方式虽然能达到效果,但版本的升级就成了问题,须要手工维护。github
另外一种方式就是咱们提到的 webjars 了。去 webjars 官网、maven 仓库、官方文档 均可以查到 swagger-ui 依赖。将依赖加入 pom.xml 后,不须要对前端进行任何配置、修改便可引入前端代码。代码的更新也很方便,修改依赖版本号便可。web
通过研究才发现,webjars 这并不是新的技术,而是利用现有的框架对静态资源的处理方案实现的。接下来咱们一块儿看看 webjars 的实现以及静态资源处理的设计方案。spring
咱们能够先来看一下 jquery webjar 的包结构:后端
jquery-3.1.0.jar └─ META-INF └─ resources └─ webjars └─ jquery └─ 3.1.0 └─ jquery.js
拿 Servlet 3 举例,应用打成 war 后,Jar(包括 WebJars)会被放在 WEB-INF/lib
目录下,而 Servlet 3 容许直接访问 WEB-INF/lib
下 jar 中的 /META-INF/resources
目录下的资源。简单来讲就是 WEB-INF/lib/{\*.jar}/META-INF/resources
下的资源能够被直接访问。
因此对于 Servlet 3,直接使用 http://localhost:8080/webjars/jquery/3.1.0/jquery.js
便可访问到 webjar 中的 jquery.js,而不用作其它的配置。
那么如何在 Spring MVC 中访问 webjars 呢?或者说,Spring MVC 如何处理静态资源?
Spring MVC 的入口是 DispatcherServlet,全部的请求都会聚集于该类,然后分发给不一样的处理类。若是不作额外的配置,是没法访问静态资源的。
若是想让 Dispatcher Servlet 直接能够访问到静态资源,最简单的方法固然是交给默认的 Servlet。
@Configuration @EnableWebMvc public class WebConfig extends WebMvcConfigurerAdapter { @Override public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) { configurer.enable(); } }
这种状况下 Spring MVC 对资源的处理与 Servlet 方式相同。
咱们能够经过很简单的配置使得 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/"); }
经过这样的配置,就成功添加了一个 PathResourceResolver
。
该 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
的资源文件。
为了简单起见,咱们假设静态资源存放在 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 可否处理,都返回其结果。
版本号的策略有两种,下面分别阐述。
指定固定值做为版本号,好比:
@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
,找到文件,直接返回。
除了指定版本号,也可使用资源的 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 的其中一种策略,所以与指定版本号的处理方式相同,再也不阐述。
不少时候,为了下降传输的数据量,能够对资源进行压缩。好比能够将 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("/**")); }
从上面的状况能够看出,Spring MVC 会对资源进行较多的处理。若是每一次请求都作这些处理,无疑会下降服务器的性能。为了不这种状况,这时能够添加 CachingResourceResolver 来解决这种问题。
CachingResourceResolver 用于缓存其它 Resource Resolver 查找到的资源。所以 CachingResourceResolver 会被放在最外层。请求先到达 CachingResourceResolver,尝试在缓存中查找,若是找到,则直接返回,若是找不到,则依次调用后面的 resolver,直到有一个 resolver 可以找到资源,CachingResourceResolver 将找到的资源缓存起来,下次请求一样的资源时,就能够从缓存中取了。
可能有人会担忧缓存资源会占用太多的内存。但实际上并无资源内容,仅仅是对资源的路径(或者说资源的抽象)进行了缓存。
开启缓存的方法很简单:
.requestChain(true)
前面的例子中都选择关闭 chain cache,缘由是缓存的存在会增长调试的难度。所以开发时能够考虑关闭该功能。
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 来知足需求。
实际上,除了 ResourceResolver,Spring MVC 还支持修改资源内容,即 Resource Transformer。
可用的 Resource Transformer 有如下几个:
他们的功能依次为:
CachingResourceResolver
咱们拿 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"
为了不客户端重复获取资源,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 缓存依旧可用。
http 缓存的更多用法参见 这里。
众所周知,使用 Spring MVC 搭建 Web 服务,不只要编写很多的代码或 XML 配置,若是开发人员使用不一样的 IDE,还要配置这些 IDE 使其得以被正确运行。
为了解决这些问题,spring.io 平台提供了 Spring Boot。Spring Boot 采用 约定优于配置 的理念,在整合已有的 Spring 组件的同时,提供了大量的默认配置。得益于这些默认配置,使用 Spring Boot,只须要编写一个 pom.xml,再加上一个 java 类,就能够跑起来一个 web 服务,若是使用 groovy,一个类文件就能跑起来 web 服务。正是因为 spring boot 带来的这种便捷的特性,被普遍应用在微服务的场景中。
如今,Spring Boot 已经很是成熟了,最好的教程固然是官方文档。
项目的建立能够为普通 maven 项目,固然还可使用 spring.io 提供的 在线建立 Spring Boot 项目 的服务建立简项目或者。固然,也能够查看本文的示例代码。
强烈推荐 看下 WebMvcAutoConfiguration 这个类,它为 Spring Boot 提供了大量的 Web 服务的默认配置。这些配置包括但不局限于:设置了主页、webjars配置、静态资源位置等。这些配置对于咱们使用配置 Web 服务颇有借鉴意义。
ps: 想要使用默认配置,无需使用 @EnaleWebMvc 注解。使用了 @EnableWebMvc 注解后 WebMvcAutoConfiguration 提供的默认配置会失效,必须提供所有配置。
最后,咱们使用 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
最后介绍一下如何查看这些配置的技巧:
经过查看 ResourceProperties 这个类能够看到,该类顶部有一个注解 @ConfigurationProperties(prefix = "spring.resources", ignoreUnknownFields = false)
。
ConfigurationProperties 是用来注入值的,prefix = "spring.resources"
表示前缀。好比咱们配置文件中的 spring.resources.static-locations=classpath:/resources
这个配置,去掉 spring.resources
这个前缀,剩下的为 static-locations
,则它的值 classpath:/resources
会被注入到 ConfigurationProperties 类的 staticLocations 成员变量中。经过这种方法,咱们就能经过编写配置文件改变类的状态而无需编写代码。固然,如何使用这些配置的关键仍是要知道这些成员变量的做用。
本文从一个新的技术点 webjars 出发,探讨了 Spring MVC 对静态资源的处理,紧接着又了解了 Spring Boot 的配置技巧。
示例代码:下载
https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching?hl=zh-cn#cache-control
http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#mvc-config-static-resources
http://qiita.com/kazuki43zoo/items/e12a72d4ac4de418ee37
http://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-spring-mvc-static-content