1. 程式人生 > >Spring boot web app專案

Spring boot web app專案

static和templates部分參考部落格:https://blog.csdn.net/wangb_java/article/details/71775637

熱部署參考部落格:https://www.cnblogs.com/cx-code/p/8686453.html

靜態頁面

spring boot專案只有src目錄,沒有webapp目錄,會將靜態訪問(html/圖片等)對映到其自動配置的靜態目錄,如下

/static

/public

/resources

/META-INF/resources

SpringBoot裡面沒有我們之前常規web開發的WebContent(WebApp),它只有src目錄

在src/main/resources下面有兩個資料夾,static和templates   springboot預設  static中放靜態頁面,而templates中放動態頁面

 

靜態頁面:

 這裡我們直接在static放一個hello.html,然後直接輸入http://localhost:8080/hello.html便能成功訪問

 

spring boot程式的static目錄預設在resources/static目錄, 打包為jar的時候,會把static目錄打包進去,這樣會存在一些問題:

  • static檔案過多,造成jar包體積過大
  • 臨時修改不方便

檢視官方文件,可以發現,static其實是可以外接的。

1 直接修改配置檔案

spring.resources.static-locations=file:///E://resources/static

2、自定義Configuration方法

@Configuration
public class StaticResourceConfiguration extends WebMvcConfigurerAdapter {
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/**").addResourceLocations("file:/path/to/my/dropbox/");
    }
}

 3、打包

修改pom,增加如下配置

<build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <finalName>workflow</finalName>
                </configuration>
            </plugin>
        </plugins>
        <resources>
            <resource>
                <directory>src/main/webapp</directory>
                <!--注意此次必須要放在此目錄下才能被訪問到 -->
                <targetPath>META-INF/resources</targetPath>
                <includes>
                    <include>**/**</include>
                </includes>
            </resource>
            <resource>
                <directory>src/main/resources</directory>
                <filtering>true</filtering>
                <includes>
                    <include>**/*</include>
                </includes>
            </resource>
            <resource>
                <directory>src/main/java</directory>
                <excludes>
                    <exclude>**/*.java</exclude>
                </excludes>
            </resource>
        </resources>

    </build>

 

1. 預設情況下, 網頁存放於static目錄下, 預設的"/"指向的是~/resouces/static/index.html文
2. 如果引入了thymeleaf, 則預設指向的地址為~/resouces/templates/index.html

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>

3.  在引入thymeleaf後, 如果仍需要訪問~/static/index.html, 則可以使用重定向 , 

return "redirect:/index.html"
程式碼樣例:

  

  import org.springframework.stereotype.Controller;
    import org.springframework.ui.Model;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    
    
    @Controller
    public class HomeCtrl {
    
        @GetMapping("/")
        public String homePage(Model model, HttpServletRequest request, HttpServletResponse response) throws IOException {
            return "/index";
        }
    
        @RequestMapping("/static")
        public String navigatorToStatic() {
            return "redirect:/static.html";
        }


    

<!DOCTYPE html>
    <html>
    <head>
        <script src="webjars/jquery/3.1.1/jquery.min.js"></script>
        <script src="webjars/bootstrap/3.3.7/js/bootstrap.min.js"></script>
        <link rel="stylesheet" href="webjars/bootstrap/3.3.7/css/bootstrap.min.css" />
    </head>
    <body>
    <div class="container"><br/>
        <div class="alert alert-success">
            Hello, <strong>BootStarp & WebJars!</strong>
        </div>
    </div>
    </body>
    </html>

在web開發中,靜態資源的訪問是必不可少的,如:圖片、js、css 等資源的訪問。

spring Boot 對靜態資源訪問提供了很好的支援,基本使用預設配置就能滿足開發需求。

一、預設靜態資源對映

Spring Boot 對靜態資源對映提供了預設配置

Spring Boot 預設將 /** 所有訪問對映到以下目錄:

classpath:/static
classpath:/public
classpath:/resources
classpath:/META-INF/resources

如:在resources目錄下新建 public、resources、static 三個目錄,並分別放入 a.jpg b.jpg c.jpg 圖片

目錄

瀏覽器分別訪問:

http://localhost:8080/a.jpg
http://localhost:8080/b.jpg
http://localhost:8080/c.jpg

均能正常訪問相應的圖片資源。那麼說明,Spring Boot 預設會挨個從 public resources static 裡面找是否存在相應的資源,如果有則直接返回。

二、自定義靜態資源對映

在實際開發中,可能需要自定義靜態資源訪問路徑,那麼可以繼承WebMvcConfigurerAdapter來實現。

第一種方式:靜態資源配置類

package com.sam.demo.conf;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

/**
 * 配置靜態資源對映
 * @author sam
 * @since 2017/7/16
 */
@Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter {

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        //將所有/static/** 訪問都對映到classpath:/static/ 目錄下
        registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/");
    }
}

重啟專案,訪問:http://localhost:8080/static/c.jpg 能正常訪問static目錄下的c.jpg圖片資源。

第二種方式:在application.properties配置

在application.properties中新增配置:

spring.mvc.static-path-pattern=/static/**

重啟專案,訪問:http://localhost:8080/static/c.jpg 同樣能正常訪問static目錄下的c.jpg圖片資源。

注意:通過spring.mvc.static-path-pattern這種方式配置,會使Spring Boot的預設配置失效,也就是說,/public /resources 等預設配置不能使用。

配置中配置了靜態模式為/static/,就只能通過/static/來訪問。

理論說明:

當前,Spring毫無疑問已經成為java後臺物件管理標準框架,除了通過IOC能夠管理我們的自定義物件的生命週期之外還提供了眾多功能繁複的可配置功能模組。但同時帶來了複雜的配置項,這對初學者而言簡直是一種災難。於是SpringBoot應運而生,Springboot的出現大大簡化了配置,主要表現在消除了web.xml和依賴注入配置的整合,處處遵循規約大於配置的思想,將初學者在繁雜的配置項中解放出來,專注於業務的實現,而不需要去關注太底層的實現。當然,也可以自己手動新增Web.xml,因為對於高階玩家而言,很多時候配置項還是很有必要的。這篇部落格不涉及具體的技術細節,只是從一個開發者的角度梳理下我的使用模式。

springboot提供了一套完整的web開發流程。前端到後臺,再到資料庫一條龍。使用spring開發一個web工程有兩條路線:

1-前後端完全分離

這種方式前端開發和後端開發完全分離,只需要協商好介面就行,前端負責開發頁面並呼叫後端介面展示資料。後端只負責提供rest介面;

2-使用springboot自帶的模板

springboot支援多種主流後端模板:

· Thymeleaf
· FreeMarker
· Velocity
· Groovy
· Mustache
· JSP
需要注意的是,雖然Spring MVC支援JSP,但是Spring Boot不建議使用JSP,因為在使用嵌入式servlet容器時,有一些使用限制。2010年後Velocity停止更新,所以這兩個都不建議使用。以上幾個模板springboot可以同時支援,什麼叫同時支援?簡而言之,springboot專案中可以同時共存多個模板,需要做的僅僅是在pom檔案中進入相關模板引擎的jar包就可以了,springboot根據模板的字尾名來決定由哪種模板引擎來解析這個動態頁面。

    • Thymeleaf : .html
    • freemaker : .ftl
    • jsp : jsp

        所以一個頁面的控制器的實現方式可能如下所示:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

@Controller //標示為controller層

public class FreeMarkerIndexController {

  @RequestMapping("/freemarkerindexController")//請求路徑,會去templates下去尋找freemarkerindex

  public String freemarkerindexController(Map<String, Object> result) {

    System.out.println("hello world");//在控制檯輸出列印hello world

    result.put("name""wangwu");//新增姓名

    result.put("sex", 1);//新增性別

    List<String> list = new ArrayList<String>();//建立一個無序集合

    list.add("zhangsan");//新增張三

    list.add("lisi");//新增李四

    result.put("userlist", list);//新增結果

    return "freemarkerindex";//返回templates的結果

  }

}

  該請求用來處理類似於:http://localhost:8080/freemarkerindex請求,但是相應的頁面模板可以隨便使用,例如你想使用jsp解析這個名為freemarkerindex這個模板,那麼就在resources/templates目錄下建立名為freemarkerindex.jsp的模板檔案就可以了,springboot會根據controller找到請求要返回的模板檔案,然後在templates目錄下找到相應的模板檔案,並且根據字尾名來判斷使用哪一種模板引擎來即系這個模板檔案,最終將解析完成之後的模板檔案返回(實際上,解析完成的模板檔案已經是一個純html靜態頁面了)。

 

注意這些模板都是後端模板,有別於前端模板(如angular)。

前端模板:前端模板通常是通過模板提供的js根據模板規定的語法規則解析html中的模板標記;

後端模板:類似於前端,一個頁面請求到達之後,後端模板引擎根據特定的語法規則解析模板中的內容,將資料填充到模板中,最終返回給瀏覽器的實際上已經是一個完整的html頁面了。

用IDE建立的Springboot web工程可能如圖做所示:

      

會發現,springboot的工程中並沒有傳統java web專案中的WEB-INF目錄以及下面的web.xml檔案,這讓人一頭霧水,以前做java web無論使用什麼框架還沒見過不寫web.xml的。

 

那麼某些情況下可能有必須使用web.xml這種場景,例如tomcat版本相容性問題,有些版本的tomcat並不支援springboot開發的web應用,或者之前老專案想要遷移到springboot上來,完全摒棄web.xml可能不太現實。那麼如何在springboot中使用原來的web.xml方式來配置servlet呢?

1-首先先在java/webapp/WEB-INF/下面加入web.xml

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

<?xml version="1.0" encoding="UTF-8"?> 

<web-app xmlns="http://java.sun.com/xml/ns/javaee" 

         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 

         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 

                      http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" 

         version="2.5"

    <context-param> 

        <param-name>contextConfigLocation</param-name> 

        <param-value>com.robbie.SpringBootTraditionalApplication</param-value> 

    </context-param> 

    <listener> 

        <listener-class>org.springframework.boot.legacy.context.web.SpringBootContextLoaderListener</listener-class

    </listener> 

    <servlet> 

        <servlet-name>appServlet</servlet-name> 

        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class

        <init-param> 

            <param-name>contextAttribute</param-name> 

            <param-value>org.springframework.web.context.WebApplicationContext.ROOT</param-value> 

        </init-param> 

        <load-on-startup>1</load-on-startup> 

    </servlet> 

    <servlet-mapping> 

        <servlet-name>appServlet</servlet-name> 

        <url-pattern>/</url-pattern> 

    </servlet-mapping> 

</web-app> 

2-將程式碼中有關Servlet的所有配置全部去掉

例如SpringBootServletInitializer來初始化Servlet,也不能用FilterRegistrationBean和ServletRegistrationBean註冊Filter和Servlet,統統遷移到web.xml中的。

3-編寫前端頁面/模板

Springboot預設的靜態資源路徑和模板頁面路徑並不是傳統的WEB-INF同目錄下。而是resource/static和resource/template下面。下面是springboot預設的頁面路徑和傳統的java web頁面路徑:

靜態web頁面請求可能為:http://localhost:8080/example/index.html

動態web頁面請求可能為:http://localhost:8080/example/jsp/index.jsp

無論是基於springboot框架的web工程還是傳統的使用servlet的web工程請求的方式其實沒有什麼本質區別,區別在於這兩種方式中靜態檔案和動態檔案存放在位置不同:

基於Springboot:靜態資源放在resource/static目錄下;動態資源放啊在resource/templates目錄下;

不基於springboot框架:靜態資源和動態資源存放目錄結構的組織有自己決定,並沒有強制要求必須放在那個目錄下面。通常將靜態資源放在和WEB-INF同目錄下

個人覺得,既然已經選擇使用springboot,那就入鄉隨俗,儘量使用springboot裡面規約使用方式,不要生硬的搞出一些很彆扭的東西出來。發散的有點遠了,回到文章剛開始提到的兩種web開發思想:前後盾完全分離和使用模板開發;這裡主要介紹下前後端分離這種方式的開發。

 

1-前後端分離開發

使用這種方式開發web專案,後端全部開發成rest API,前端交給專業的前端開發工程師去開發,後端和前端之前完全解耦,前端工程師可以根據自己的喜好選擇前端開發框架,例如angular、react、vue等。

靜態資源存放路徑:src\main\resources\static\index.html

靜態資源訪問路徑:http://localhost:8080/index.html

js、css、圖片等靜態資源的訪問方式閾值類似;

動態請求全部按照restAPI的方式進行進行響應。

2-使用模板開發

@RequestMapping(value="/")
public String defaultHandler(){
    return "thymeleaf";
}

這個例子的是意思是對於所有的請求都將會返回名為thymeleaf的模板檔案。  

2. 預備知識

2.1 Servlet 3

我們可以先來看一下 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 如何處理靜態資源?

2.2 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 方式相同。

3. 基礎

我們可以通過很簡單的配置使得 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 的資原始檔。

4. 進階

4.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 能否處理,都返回其結果。

版本號的策略有兩種,下面分別闡述。

4.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,找到檔案,直接返回。

4.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 的其中一種策略,因此與指定版本號的處理方式相同,不再闡述。

4.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("/**"));
           
}

4.3 chain cache

從上面的情況可以看出,Spring MVC 會對資源進行較多的處理。如果每一次請求都做這些處理,無疑會降低伺服器的效能。為了避免這種情況,這時可以新增 CachingResourceResolver 來解決這種問題。

圖片

CachingResourceResolver 用於快取其它 Resource Resolver 查詢到的資源。因此 CachingResourceResolver 會被放在最外層。請求先到達 CachingResourceResolver,嘗試在快取中查詢,如果找到,則直接返回,如果找不到,則依次呼叫後面的 resolver,直到有一個 resolver 能夠找到資源,CachingResourceResolver 將找到的資源快取起來,下次請求同樣的資源時,就可以從快取中取了。

可能有人會擔心快取資源會佔用太多的記憶體。但實際上並沒有資源內容,僅僅是對資源的路徑(或者說資源的抽象)進行了快取。

開啟快取的方法很簡單:

.requestChain(true)

前面的例子中都選擇關閉 chain cache,原因是快取的存在會增加除錯的難度。因此開發時可以考慮關閉該功能。

4.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 來滿足需求。

4.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"

4.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 快取依舊可用。

http 快取的更多用法參見 這裡

5. 使用 Spring Boot 配置

眾所周知,使用 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 成員變數中。通過這種方法,我們就能通過編寫配置檔案改變類的狀態而無需編寫程式碼。當然,如何使用這些配置的關鍵還是要知道這些成員變數的作用。

6. 總結

本文從一個新的技術點 webjars 出發,探討了 Spring MVC 對靜態資源的處理,緊接著又瞭解了 Spring Boot 的配置技巧。

示例程式碼:下載

7. 參考

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