知識庫 / Spring / Spring MVC RSS 訂閱

Spring MVC 中緩存靜態資源

Spring MVC
HongKong
5
02:47 PM · Dec 06 ,2025

1. 概述

本文重點介紹了在 Spring Boot 和 Spring MVC 中提供靜態資產(如 JavaScript 和 CSS 文件)時的緩存機制。

我們還將探討“完美緩存”的概念,即確保 – 當文件更新時 – 舊版本不會錯誤地從緩存中提供。

2. 緩存靜態資源

為了使靜態資源可緩存,我們需要配置其對應的資源處理器。

以下是一個簡單的示例,展示如何執行此操作——通過將 Cache-Control 標頭設置為響應,值為 max-age=31536000,從而使瀏覽器在一年內使用該文件的緩存版本:

@EnableWebMvc
public class MvcConfig implements WebMvcConfigurer {
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/js/**") 
                .addResourceLocations("/js/") 
                .setCacheControl(CacheControl.maxAge(365, TimeUnit.DAYS));
    }
}

由於緩存有效期的原因,我們採用瞭如此長的有效期,因為我們希望客户端在文件更新之前,能夠繼續使用緩存的文件的版本。365天是我們根據 RFC 規範中 Cache-Control 頭的最大可用的有效期。

因此,當客户端首次請求 foo.js 時,他將通過網絡接收整個文件(在本例中為 37 字節)並返回狀態碼 200 OK。響應將包含以下標頭,用於控制緩存行為:

Cache-Control: max-age=31536000

這會指示瀏覽器使用一年作為過期時長緩存該文件,結果如下:

當客户端再次請求同一文件時,瀏覽器將不會向服務器發出新的請求。而是直接從緩存中提供該文件,從而避免網絡往返,從而使頁面加載速度更快:

Chrome 瀏覽器用户在測試時需要小心,因為 Chrome 在通過按下屏幕上的刷新按鈕或按下 F5 鍵刷新頁面時,不會使用緩存。您需要按下地址欄中的 Enter 鍵來觀察緩存行為。有關更多信息,請訪問 此處

2.1. Spring Boot

為了在 Spring Boot 中自定義 <em Cache-Control</em> 請求頭,我們可以使用 spring.resources.cache.cachecontrol 屬性命名空間下的屬性。例如,要將 <em max-age</em> 設置為一年,可以在我們的 <em application.properties</em> 中添加以下內容:

spring.resources.cache.cachecontrol.max-age=365d

這適用於所有由 Spring Boot 提供靜態資源。因此,如果我們只想為請求的子集應用緩存策略,我們應該使用標準的 Spring MVC 方式。

除了max-age之外,還可以通過Cache-Control參數進行自定義,例如no-storeno-cache,並使用類似的配置屬性。

3. 靜態資源版本控制

使用緩存來提供靜態資源可以極大地加快頁面加載速度,但它也存在一個重要的限制。當您更新文件時,客户端將不會獲得文件的最新版本,因為它不會與服務器檢查文件是否已更新,而是直接從瀏覽器緩存中提供該文件。

以下是我們如何確保瀏覽器僅在文件更新時從服務器獲取文件的步驟:

  • 在 URL 中包含版本號來提供文件。例如,foo.js 應提供在 /js/foo-46944c7e3a9bd20cc30fdc085cae46f2.js 下。
  • 使用新的 URL 更新指向文件的鏈接。
  • 在文件更新時更新 URL 中的版本號。例如,當 foo.js 更新時,它現在應提供在 /js/foo-a3d8d7780349a12d739799e9aa7d2623.js 下。

由於頁面具有指向不同 URL 的鏈接,因此客户端會在更新時從服務器請求該文件,因此瀏覽器不會使用其緩存。如果文件未更新,其版本(即其 URL)將保持不變,客户端將繼續使用該文件的緩存。

通常,我們需要手動執行所有這些操作,但 Spring 支持這些功能,包括為每個文件計算哈希值並將其附加到 URL 中。讓我們看看如何配置我們的 Spring 應用程序以自動執行這些操作。

3.1. 在 URL 下版本服務

我們需要將 VersionResourceResolver 添加到路徑中,以便在 URL 中使用更新的版本字符串的情況下,在其中提供文件。

@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
    registry.addResourceHandler("/js/**")
            .addResourceLocations("/js/")
            .setCacheControl(CacheControl.maxAge(365, TimeUnit.DAYS))
            .resourceChain(false)
            .addResolver(new VersionResourceResolver().addContentVersionStrategy("/**"));
}

我們這裏採用內容版本策略。位於 /js 文件夾中的每個文件都將通過計算其內容產生的 URL 進行服務。這種方法被稱為指紋識別(fingerprinting)。例如,foo.js 現在將通過 /js/foo-46944c7e3a9bd20cc30fdc085cae46f2.js 這個 URL 進行服務。

通過這種配置,當客户端請求 http://localhost:8080/js/46944c7e3a9bd20cc30fdc085cae46f2.js 時。

curl -i http://localhost:8080/js/foo-46944c7e3a9bd20cc30fdc085cae46f2.js

服務器將通過響應 HTTP 緩存控制頭來告知客户端瀏覽器該文件應緩存一年。

HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Last-Modified: Tue, 09 Aug 2016 06:43:26 GMT
Cache-Control: max-age=31536000

3.2. Spring Boot

為了在 Spring Boot 中啓用基於內容的版本控制,只需在 <a href="https://github.com/spring-projects/spring-boot/blob/bb568c5bffcf70169245d749f3642bfd9dd33143/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration.java#L532">spring.resources.chain.strategy.content`` 屬性命名空間中進行少量配置。例如,我們可以通過添加以下配置來實現與之前相同的效果:

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

類似於 Java 配置,此選項啓用基於內容的版本控制,應用於所有與 /** 路徑模式匹配的資產。

3.3. 使用新 URL 更新鏈接

在我們在 URL 中插入版本號之前,我們可以使用簡單的 <em >script</em > 標籤導入 <em >foo.js</em >:

<script type="text/javascript" src="/js/foo.js">

現在我們同時為同一文件提供 URL 上的版本,需要將其反映在頁面上。

<script type="text/javascript" 
  src="<em>/js/foo-46944c7e3a9bd20cc30fdc085cae46f2.js</em>">

處理這些冗長的路徑變得非常繁瑣。Spring 提供了更好的解決方案來解決這個問題。我們可以使用 ResourceUrlEncodingFilter 和 JSTL 的 url 標籤,將帶有版本號的鏈接 URL 重寫為新的 URL。

ResourceURLEncodingFilter 可以在 web.xml 中像往常一樣進行註冊:

<filter>
    <filter-name>resourceUrlEncodingFilter</filter-name>
    <filter-class>
        org.springframework.web.servlet.resource.ResourceUrlEncodingFilter
    </filter-class>
</filter>
<filter-mapping>
    <filter-name>resourceUrlEncodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

JSTL 核心標籤庫需要在我們的 JSP 頁面中導入,才能使用 url 標籤:

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>

然後,我們可以使用 url 標籤將 foo.js 導入如下:

<script type="text/javascript" src="<c:url value="/js/foo.js" />">

當此 JSP 頁面渲染時,文件 URL 正確地被重寫,以包含版本信息:

<script type="text/javascript" src="/js/foo-46944c7e3a9bd20cc30fdc085cae46f2.js">

3.4. URL 中版本號的處理

當文件更新時,系統會自動重新計算版本號,並將文件服務在包含新版本號的 URL 下。 這無需我們進行任何額外操作,<em style="font-style: italic;">VersionResourceResolver</em> 會自動處理這一切。

4. 修復 CSS 鏈接

CSS 文件可以通過使用 <em @import </em>> 指令導入其他 CSS 文件。例如,<em myCss.css </em>> 文件導入 <em another.css </em>> 文件:

@import "another.css";

這通常會導致版本化靜態資源出現問題,因為瀏覽器會為 another.css 文件發出請求,但該文件被服務在帶有版本化路徑的路徑下,例如 another-9556ab93ae179f87b178cfad96a6ab72.css

為了解決這個問題並確保請求正確的路徑,我們需要將 CssLinkResourceTransformer 引入資源處理器的配置中:

@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
    registry.addResourceHandler("/resources/**")
            .addResourceLocations("/resources/", "classpath:/other-resources/")
            .setCacheControl(CacheControl.maxAge(365, TimeUnit.DAYS))
            .resourceChain(false)
            .addResolver(new VersionResourceResolver().addContentVersionStrategy("/**"))
            .addTransformer(new CssLinkResourceTransformer());
}

這會修改 myCss.css 的內容,並將其導入語句替換為以下內容:

@import "another-9556ab93ae179f87b178cfad96a6ab72.css";

5. 結論

充分利用 HTTP 緩存可以顯著提升網站性能,但同時在利用緩存時避免提供過時的資源可能會比較繁瑣。

在本文中,我們實施了一種良好的策略,在利用 Spring MVC 提供靜態資源的同時,並對文件更新時進行緩存清除。

發佈 評論

Some HTML is okay.