96. Spring Boot之靜態資源版本對映(解決js/css快取問題)
我們在之前有一篇文章中講過【處理靜態資源】,但是在實際開發中,我們會發現我們所瞭解到的知識遠遠不夠我們使用,今天這節就是在實際開發當中對碰到的問題進行一定的講解和解決。
問題的提出:我們對於我們編寫的js和css檔案,經常會做一些改變,由於瀏覽器快取,使用者本地的資源還是舊資源,一般為了解決這種情況導致的問題,我們會可能會選擇在資原始檔後面加上引數“版本號”或其他方式。
使用版本號引數,如:
<scripttype="text/javascript"src="/js/common.js?v=1.0.1"></script>
使用這種方式,當我們檔案修改後,手動修改版本號來達到URL檔案不被瀏覽器快取的目的。同樣也存在很多檔案都需要修改的問題,或者有的人會增加時間戳的形式,這樣是最不可取的,每次瀏覽器都需要為伺服器增加了不必要的壓力。Spring在解決這種問題方面,提供了2中解決方式。我們看看本節的大綱吧。
(2)使用webjars
(3)Spring 靜態資源版本對映之資源名稱md5方式
(4)Spring 靜態資源版本對映之資源版本號方式
(5)md5與版本號方式的處理原理
(6)總結
接下來我們看看具體是怎麼操作的。
(1)回顧預設資源對映
預設配置的 /** 對映到 /static (或/public、/resources、/META-INF/resources)
其中預設配置的 /webjars/** 對映到 classpath:/META-INF/resources/webjars/
(2)使用webjars
先說一下什麼是webjars?我們在Web開發中,前端頁面中用了越來越多的JS或CSS,如jQuery等等,平時我們是將這些Web資源拷貝到Java的目錄下,這種通過人工方式拷貝可能會產生版本誤差,拷貝版本錯誤,前端頁面就無法正確展示。
WebJars 就是為了解決這種問題衍生的,將這些Web前端資源打包成Java的Jar包,然後藉助Maven這些依賴庫的管理,保證這些Web資源版本唯一性。
WebJars 就是將js, css 等資原始檔放到 classpath:/META-INF/resources/webjars/ 中,然後打包成jar 釋出到maven倉庫中。
以jQuery為例,檔案存放結構為:
META-INF/resources/webjars/jquery/2.1.4/jquery.js
META-INF/resources/webjars/jquery/2.1.4/jquery
META-INF/resources/webjars/jquery/2.1.4/jquery.min.map
META-INF/resources/webjars/jquery/2.1.4/webjars-requirejs.js
使用方式就是在pom.xml檔案新增配置:
<dependency>
<groupId>org.webjars</groupId>
<artifactId>jquery</artifactId>
<version>2.1.4</version>
</dependency>
Spring Boot 預設將 /webjars/** 對映到 classpath:/META-INF/resources/webjars/ ,結合我們上面講到的訪問資源的規則,便可以得知我們在頁面中引入jquery.js的方法為:
<script type="text/javascript" src="/webjars/jquery/2.1.4/jquery.js"></script>
版本號統一管理
但是我們實際開發中,可能會遇到升級版本號的情況,如果我們有100多個頁面,幾乎每個頁面上都有按上面引入jquery.js 那麼我們要把版本號更換為3.0.0,一個一個替換顯然不是最好的辦法。 使用webjars的webjars-locator就可以解決以上的問題,那麼具體要怎麼操作呢?
首先在pom.xml新增webjars-locator的依賴:
<dependency>
<groupId>org.webjars</groupId>
<artifactId>webjars-locator</artifactId>
</dependency>
增加一個WebJarsController,這個Controller會將以webjarslocator路徑攔截,然後重新組裝處理,具體程式碼如下:
package com.kfit;
import javax.servlet.http.HttpServletRequest;
import org.springframework.core.io.ClassPathResource;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.HandlerMapping;
import org.webjars.WebJarAssetLocator;
/**
* 這個Controller會將以webjarslocator路徑攔截,然後重新組裝處理
* @author Angel --守護天使
* @version v.0.1
* @date 2016年12月1日
*/
@Controller
public class WebJarsController {
privatefinal WebJarAssetLocator assetLocator = new WebJarAssetLocator();
@ResponseBody
@RequestMapping("/webjarslocator/{webjar}/**")
public ResponseEntity<Object> locateWebjarAsset(@PathVariable String webjar, HttpServletRequest request) {
try {
String mvcPrefix = "/webjarslocator/" + webjar + "/"; // This prefix must match the mapping path!
String mvcPath = (String)request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE);
String fullPath = assetLocator.getFullPath(webjar,mvcPath.substring(mvcPrefix.length()));
return new ResponseEntity<>(new ClassPathResource(fullPath), HttpStatus.OK);
} catch (Exception e) {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
}
}
最後在頁面中使用的方式:
<script type="text/javascript" src="/webjarslocator/jquery/jquery.js"></script>
(3)Spring 靜態資源版本對映之資源名稱md5方式
Spring 預設提供了靜態資源版本對映的支援。
當我們的資源內容發生改變時,由於瀏覽器快取,使用者本地的資源還是舊資源,為了防止這種情況發生導致的問題。我們可能會選擇在資原始檔後面加上引數“版本號”或其他方式。
<scripttype="text/javascript"src="/js/demo.js?v=1.0.1"></script>
使用這種方式,當我們檔案修改後,手工修改版本號來達到URL檔案不被瀏覽器快取的目的。同樣也存在很多檔案都需要修改的問題。或者有的人會增加時間戳的方式,這樣我認為是最不可取的,每次瀏覽器都要請求為伺服器增加了不必要的壓力。
然而Spring在解決這種問題方面,提供了2種解決方式。 第一種方式就是MD5的方式,我們看下具體怎麼操作。
第一步:修改 application.properties 配置檔案
spring.resources.chain.strategy.content.enabled=true
spring.resources.chain.strategy.content.paths=/**
第二步:建立 ResourceUrlProviderController 檔案
package com.kfit;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.servlet.resource.ResourceUrlProvider;
/**
*
* @author Angel --守護天使
* @version v.0.1
* @date 2016年12月1日
*/
@ControllerAdvice
public class ResourceUrlProviderController {
@Autowired
private ResourceUrlProvider resourceUrlProvider;
@ModelAttribute("urls")
public ResourceUrlProvider urls() {
return this.resourceUrlProvider;
}
}
第三步:在頁面中進行使用:
<scripttype="text/javascript"src="${urls.getForLookupPath('/js/demo.js') }"></script>
如果使用的thymeleaf模板引擎的話,那麼需要這麼進行編寫:
<scripttype="text/javascript"th:src="${urls.getForLookupPath('/js/demo.js') }"></script>
注:這個博主經過測試,證明可用。
當我們訪問頁面後,HTML中實際生成的程式碼為:
<scripttype="text/javascript"src="/js/demo--ef8d9e1da763788be348c78ea32a3c6d.js"></script>
(4)Spring 靜態資源版本對映之資源版本號方式
資源版本號方式對所有資源的統一版本控制,不像上面一個md5是針對檔案的。
除了在 application.properties中的配置有所區別,頁面使用和md5的一樣。
spring.resources.chain.strategy.fixed.enabled=true
spring.resources.chain.strategy.fixed.paths=/js/**,/v1.0.0/**
spring.resources.chain.strategy.fixed.version=v1.0.0
這樣配置後,以上面 common.js 為例,實際頁面中生成的HTML程式碼為:
<scripttype="text/javascript"src="/v1.0.0/js/demo.js"></script>
(5)md5與版本號方式的處理原理
頁面中首先會呼叫urls.getForLookupPath方法,返回一個/v1.0.0/js/demo.js或/css/demo-c6b7da8fffc9be141b48c073e39c7340.js然後瀏覽器發起請求。
當請求的地址為md5方式時,會嘗試url中的檔名中是否包含-,如果包含會去掉後面這部分,然後去對映的目錄(如/static/)查詢/js/common.js檔案,如果能找到就返回。
當請求的地址為版本號方式時,會在url中判斷是否存在/v1.0.0 ,如果存在,則先從URL中把 /v1.0.0 去掉,然後再去對映目錄查詢對應檔案,找到就返回。
(6)總結
有這麼多方式來管理我們的資原始檔,然而在實際應用中雖然也都有可能用到(存在就有存在的道理嘛),但是憑藉個人經驗來說。
<1>. 我們使用第三方的庫時,建議使用webjars的方式,通過動態版本號(webjars-locator 的方式)來使用(因為第三方庫在專案開發中變動頻率很小,即便是變動也是版本號的修改)。
<2>. 我們使用自己存放在靜態資源對映目錄中的資源的時候,建議使用md5 資原始檔名的方式來使用(專案開發中一些css、js檔案會經常修改)。
<3>. 專案素材檔案建議放到 classpath:/static (或其他)目錄中,打包在專案中,通過CMS維護的一些圖片和資源,我們使用配置引用到具體的磁碟絕對路徑來使用。
<4>. 注意使用md5檔名方式的時候,Spring 是有快取機制的,也就是說,在服務不重啟的情況下,你去變動修改這些資原始檔,其檔名的md5值並不會改變,只有重啟服務再次訪問才會生效。如果需要每次都獲取實際檔案的md5值,需要重寫相關類來實現,我們不建議這樣做,因為一直去計算檔案md5值是需要效能代價的。