前一段時間,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