Spring Boot 靜態資源處理

目錄(?)[-]javascript

  1. 默認資源映射
  2. 自定義資源映射
    1. 自定義目錄
    2. 使用外部目錄
    3. 經過配置文件配置
  3. 頁面中使用
  4. 使用webjars
    1. 簡單應用
    2. 版本號統一管理
  5. 靜態資源版本管理
  6. 總結

Spring Boot 默認爲咱們提供了靜態資源處理,使用 WebMvcAutoConfiguration 中的配置各類屬性。css

建議你們使用Spring Boot的默認配置方式,若是須要特殊處理的再經過配置進行修改。前端

若是想要本身徹底控制WebMVC,就須要在@Configuration註解的配置類上增長@EnableWebMvc(@SpringBootApplication 註解的程序入口類已經包含@Configuration),增長該註解之後WebMvcAutoConfiguration中配置就不會生效,你須要本身來配置須要的每一項。這種狀況下的配置仍是要多看一下WebMvcAutoConfiguration類。java

咱們既然是快速使用Spring Boot,並不想過多的本身再從新配置。本文仍是主要針對Spring Boot的默認處理方式,部分配置在application 配置文件中(.properties 或 .yml)jquery

默認資源映射

咱們在啓動應用的時候,能夠在控制檯中看到以下信息:web

2016-01-08 09:29:30.362  INFO 24932 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2016-01-08 09:29:30.362  INFO 24932 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2016-01-08 09:29:30.437  INFO 24932 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]

其中默認配置的 /** 映射到 /static (或/public、/resources、/META-INF/resources) 
其中默認配置的 /webjars/** 映射到 classpath:/META-INF/resources/webjars/ 
PS:上面的 static、public、resources 等目錄都在 classpath: 下面(如 src/main/resources/static)。spring

若是我按以下結構存放相同名稱的圖片,那麼Spring Boot 讀取圖片的優先級是怎樣的呢? 
以下圖: 
這裏寫圖片描述瀏覽器

當咱們訪問地址 http://localhost:8080/fengjing.jpg 的時候,顯示哪張圖片?這裏博主能夠直接告訴你們,優先級順序爲:META/resources > resources > static > public 
若是咱們想訪問pic2.jpg,請求地址 http://localhost:8080/img/pic2.jpg緩存

自定義資源映射

上面咱們介紹了Spring Boot 的默認資源映射,通常夠用了,那咱們如何自定義目錄? 
這些資源都是打包在jar包中的,而後實際應用中,咱們還有不少資源是在管理系統中動態維護的,並不可能在程序包中,對於這種隨意指定目錄的資源,如何訪問?springboot

自定義目錄

以增長 /myres/* 映射到 classpath:/myres/* 爲例的代碼處理爲: 
實現類繼承 WebMvcConfigurerAdapter 並重寫方法 addResourceHandlers (對於 WebMvcConfigurerAdapter 上篇介紹攔截器的文章中已經有提到)

package org.springboot.sample.config;

import org.springboot.sample.interceptor.MyInterceptor1;
import org.springboot.sample.interceptor.MyInterceptor2;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

@Configuration
public class MyWebAppConfigurer 
        extends WebMvcConfigurerAdapter {

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/myres/**").addResourceLocations("classpath:/myres/");
        super.addResourceHandlers(registry);
    }

}

 訪問myres 文件夾中的fengjing.jpg 圖片的地址爲 http://localhost:8080/myres/fengjing.jpg 
這樣使用代碼的方式自定義目錄映射,並不影響Spring Boot的默認映射,能夠同時使用。

若是咱們將/myres/* 修改成 /* 與默認的相同時,則會覆蓋系統的配置,能夠屢次使用 addResourceLocations 添加目錄,優先級先添加的高於後添加的。

// 訪問myres根目錄下的fengjing.jpg 的URL爲 http://localhost:8080/fengjing.jpg (/** 會覆蓋系統默認的配置)
// registry.addResourceHandler("/**").addResourceLocations("classpath:/myres/").addResourceLocations("classpath:/static/");

 其中 addResourceLocations 的參數是動參,能夠這樣寫 addResourceLocations(「classpath:/img1/」, 「classpath:/img2/」, 「classpath:/img3/」);

使用外部目錄

若是咱們要指定一個絕對路徑的文件夾(如 H:/myimgs/ ),則只須要使用 addResourceLocations 指定便可。

// 能夠直接使用addResourceLocations 指定磁盤絕對路徑,一樣能夠配置多個位置,注意路徑寫法須要加上file:
registry.addResourceHandler("/myimgs/**").addResourceLocations("file:H:/myimgs/");

 經過配置文件配置

上面是使用代碼來定義靜態資源的映射,其實Spring Boot也爲咱們提供了能夠直接在 application.properties(或.yml)中配置的方法。 
配置方法以下:

# 默認值爲 /**
spring.mvc.static-path-pattern=
# 默認值爲 classpath:/META-INF/resources/,classpath:/resources/,classpath:/static/,classpath:/public/ 
spring.resources.static-locations=這裏設置要指向的路徑,多個使用英文逗號隔開,

使用 spring.mvc.static-path-pattern 能夠從新定義pattern,如修改成 /myres/** ,則訪問static 等目錄下的fengjing.jpg文件應該爲 http://localhost:8080/myres/fengjing.jpg ,修改以前爲 http://localhost:8080/fengjing.jpg 
使用 spring.resources.static-locations 能夠從新定義 pattern 所指向的路徑,支持 classpath: 和 file: (上面已經作過說明) 
注意 spring.mvc.static-path-pattern 只能夠定義一個,目前不支持多個逗號分割的方式。

頁面中使用

上面幾個例子中也已經說明了怎麼訪問靜態資源,其實在頁面中使用不論是jsp仍是freemarker,並無什麼特殊之處,也咱們平時開發web項目同樣便可。 
下面是個人index.jsp:

<body>
    <img alt="讀取默認配置中的圖片" src="${pageContext.request.contextPath }/pic.jpg">
    <br/>
    <img alt="讀取自定義配置myres中的圖片" src="${pageContext.request.contextPath }/myres/fengjing.jpg">
</body>

使用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.min.js
META-INF/resources/webjars/jquery/2.1.4/jquery.min.map
META-INF/resources/webjars/jquery/2.1.4/webjars-requirejs.js

 Spring Boot 默認將 /webjars/** 映射到 classpath:/META-INF/resources/webjars/ ,結合咱們上面講到的訪問資源的規則,即可以得知咱們在JSP頁面中引入jquery.js的方法爲:

<script type="text/javascript" src="${pageContext.request.contextPath }/webjars/jquery/2.1.4/jquery.js"></script>

想實現這樣,咱們只須要在pom.xml 文件中添加jquery的webjars 依賴便可,以下:

<dependency>
    <groupId>org.webjars</groupId>
    <artifactId>jquery</artifactId>
    <version>2.1.4</version>
</dependency>

 版本號統一管理

可是咱們實際開發中,可能會遇到升級版本號的狀況,若是咱們有100多個頁面,幾乎每一個頁面上都有按上面引入jquery.js 那麼咱們要把版本號更換爲3.0.0,一個一個替換顯然不是最好的辦法。 
如何來解決?按以下方法處理便可。

首先在pom.xml 中添加依賴:

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

 而後增長一個WebJarsController:

package org.springboot.sample.controller;

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;

/**
 * 處理WebJars,自動讀取版本號
 *
 * @author   單紅宇(365384722)
 * @myblog  http://blog.csdn.net/catoop/
 * @create    2016年1月8日
 */
@Controller
public class WebJarsController {

     private final 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="${pageContext.request.contextPath }/webjarslocator/jquery/jquery.js"></script>

靜態資源版本管理

Spring 默認提供了靜態資源版本映射的支持。 
當咱們的資源內容發生改變時,因爲瀏覽器緩存,用戶本地的資源仍是舊資源,爲了防止這種狀況發生致使的問題。咱們可能會選擇在資源文件後面加上參數「版本號」或其餘方式。

使用版本號參數,如:

<script type="text/javascript" src="${pageContext.request.contextPath }/js/common.js?v=1.0.1"></script>

用這種方式,當咱們文件修改後,手工修改版本號來達到URL文件不被瀏覽器緩存的目的。一樣也存在不少文件都須要修改的問題。或者有的人會增長時間戳的方式,這樣我認爲是最不可取的,每次瀏覽器都要請求爲服務器增長了沒必要要的壓力。

然而Spring在解決這種問題方面,提供了2種解決方式。 
* 資源名稱md5方式 * 
1. 修改 application.properties 配置文件(或.yml)

spring.resources.chain.strategy.content.enabled=true
spring.resources.chain.strategy.content.paths=/**

全部 /** 請求的靜態資源都會被處理。

  1. 建立 ResourceUrlProviderController 文件
package org.springboot.sample.config;

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;

/**
 * 處理靜態資源URL
 *
 * @author 單紅宇(365384722)
 * @myblog http://blog.csdn.net/catoop/
 * @create 2016年1月8日
 */
@ControllerAdvice
public class ResourceUrlProviderController {

    @Autowired
    private ResourceUrlProvider resourceUrlProvider;

    @ModelAttribute("urls")
    public ResourceUrlProvider urls() {
        return this.resourceUrlProvider;
    }
}

 在頁面中使用的寫法

<script type="text/javascript" src="${pageContext.request.contextPath }${urls.getForLookupPath('/js/common.js') }"></script>

 當咱們訪問頁面後,HTML中實際生成的代碼爲:

<script type="text/javascript" src="/myspringboot/js/common-c6b7da8fffc9be141b48c073e39c7340.js"></script>
  • 其中 /myspringboot 爲我這個項目的 contextPath

* 資源版本號方式 * 
該方式本人以爲並沒有多大意義,也不作詳細說明,這是對全部資源的統一版本控制,不像上面一個md5是針對文件的。 
除了在 application.properties(或.yml)中的配置有所區別,頁面使用和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代碼爲:

<script type="text/javascript" src="/myspringboot/v1.0.0/js/common.js"></script>

* md5與版本號方式的處理原理 * 
頁面中首先會調用urls.getForLookupPath方法,返回一個/v1.0.0/js/common.js或/css/common-c6b7da8fffc9be141b48c073e39c7340.js 
而後瀏覽器發起請求。 
當請求的地址爲md5方式時,會嘗試url中的文件名中是否包含-,若是包含會去掉後面這部分,而後去映射的目錄(如/static/)查找/js/common.js文件,若是能找到就返回。

當請求的地址爲版本號方式時,會在url中判斷是否存在/v1.0.0 ,若是存在,則先從URL中把 /v1.0.0 去掉,而後再去映射目錄查找對應文件,找到就返回。

總結

有這麼多方式來管理咱們的資源文件,然而在實際應用中雖然也都有可能用到(存在就有存在的道理嘛),可是憑藉我的經驗來講。 
1. 咱們使用第三方的庫時,建議使用webjars的方式,經過動態版本號(webjars-locator 的方式)來使用(由於第三方庫在項目開發中變更頻率很小,即使是變更也是版本號的修改)。 
2. 咱們使用本身存放在靜態資源映射目錄中的資源的時候,建議使用md5 資源文件名的方式來使用(項目開發中一些css、js文件會常常修改)。 
3. 項目素材文件建議放到 classpath:/static (或其餘)目錄中,打包在項目中,經過CMS維護的一些圖片和資源,咱們使用配置引用到具體的磁盤絕對路徑來使用。 
4. 注意使用md5文件名方式的時候,Spring 是有緩存機制的,也就是說,在服務不重啓的狀況下,你去變更修改這些資源文件,其文件名的md5值並不會改變,只有重啓服務再次訪問纔會生效。若是須要每次都獲取實際文件的md5值,須要重寫相關類來實現,咱們不建議這樣作,由於一直去計算文件md5值是須要性能代價的。深刻 Spring 系列之靜態資源處理

    上一篇Spring Boot 攔截器

   下一篇Spring Boot 啓動加載數據 CommandLineRunner

 

  相關文章推薦

相關文章
相關標籤/搜索