1. 先來看幾個問題
1. 檔案更新後無法及時生效
瀏覽器快取機制是爲了加快載入速度和減少伺服器壓力,但有時會帶來問題。當 JavaScript 檔案更新後,如果瀏覽器從快取中載入舊版本,使用者就無法看到最新的功能或修復的 Bug。舉個例子:開發者釋出了新版本的前端程式碼,修復了一個關鍵問題,但使用者的瀏覽器仍然使用快取的舊程式碼,導致問題依然存在。使用者可能以為網站沒修復或出現新問題,從而影響使用者體驗。
2. 前後端不一致
在 Java Web 應用中,JavaScript 通常用於與後端服務互動。如果 JavaScript 程式碼版本和後端邏輯不一致,可能導致不相容問題。舉個例子:後端介面的請求格式發生變化,但瀏覽器仍然使用舊的 JavaScript 程式碼,結果是客戶端與伺服器之間通訊失敗,產生錯誤。
3. 影響除錯與開發
在開發和除錯環境中,快取會導致程式碼變更後無法即時看到效果,這對於除錯過程非常不便。開發者可能會在除錯中發現修改的程式碼沒有被應用,導致浪費時間。舉個例子:開發者修改了 JavaScript 檔案,但由於快取,瀏覽器繼續執行舊的程式碼,開發者無法驗證新程式碼是否正確,甚至可能以為程式碼本身有問題。
4. 安全問題
舊的快取可能會暴露系統之前存在的漏洞。即使後端做了升級,修復了安全漏洞,但如果瀏覽器載入了舊的 JavaScript 檔案,可能仍然會受到攻擊。舉個例子:假設某個版本的 JavaScript 中存在一個 XSS 漏洞。雖然新版本已經修復了這個漏洞,但瀏覽器快取的舊檔案仍然暴露在攻擊風險中。
所以,如果前端頁面無法及時響應更新(如修復 Bug、最佳化功能等),使用者體驗可能會受到負面影響。特別是在進行產品版本迭代時,快取問題可能會使新功能看起來未上線,影響使用者的使用體驗和滿意度。
2. 那要如何解決呢?
在 Java Web 開發中,爲了確保 JavaScript 檔案(或任何靜態資源)不被瀏覽器快取,可以使用以下幾種經驗:
1. 使用版本號或時間戳
為 JavaScript 檔案的 URL 新增一個版本號或時間戳,使得每次檔案更新後 URL 不同,這樣瀏覽器會認為是新的資源,從而重新載入。比如:
<script src="app.js?v=1.0.1"></script>
或者使用動態的時間戳:
<script src="app.js?t=<%= System.currentTimeMillis() %>"></script>
透過這種方式,每次生成不同的查詢引數,瀏覽器會認為這是一個新的檔案,不會從快取中讀取。
2. 設定 HTTP 響應頭
在 Java 後端(比如 Spring Boot 或 Servlet),可以透過設定 HTTP 頭來控制快取。常見的 HTTP 頭包括:
Cache-Control
: 控制快取行為。Pragma
: 控制快取行為(主要用於相容 HTTP/1.0)。Expires
: 設定資源過期的時間。
示例程式碼(Spring Boot 過濾器):
import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletResponse; import java.io.IOException; public class NoCacheFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletResponse httpResponse = (HttpServletResponse) response; httpResponse.setHeader("Cache-Control", "no-store, no-cache, must-revalidate, max-age=0"); httpResponse.setHeader("Pragma", "no-cache"); httpResponse.setHeader("Expires", "0"); chain.doFilter(request, response); } @Override public void init(FilterConfig filterConfig) throws ServletException {} @Override public void destroy() {} }
3. 配置靜態資源的快取策略
在 Spring Boot 專案中,可以透過配置類來定義靜態資源的快取策略。例如:
import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/js/**") .addResourceLocations("classpath:/static/js/") .setCachePeriod(0); // 0 表示不快取 } }
透過 setCachePeriod(0)
設定快取週期為 0,強制瀏覽器每次都從伺服器獲取最新的 JavaScript 檔案。
4. 使用 ETag
或 Last-Modified
在 HTTP 響應中設定 ETag
或 Last-Modified
,這樣瀏覽器在每次請求時都會詢問伺服器資源是否更新。如果沒有變化,伺服器會返回 304 狀態碼,從而避免不必要的重新載入。
示例(設定 Last-Modified
):
httpResponse.setDateHeader("Last-Modified", System.currentTimeMillis());
上面的幾種方法,能確保瀏覽器及時獲取最新版本的 JavaScript 檔案,不使用快取的舊版本。
關於一些思考
問題來了,那什麼時候可以使用快取?
雖然快取可能帶來這些問題,但並不意味著快取永遠不好。在某些場景中,使用瀏覽器快取可以顯著提升效能:
靜態資源(如 JavaScript、CSS 檔案)較少更改時,快取可以顯著減少網路請求,提升頁面載入速度。
確保在更新時有效控制快取機制(比如用檔案的版本號或雜湊值作為檔名的一部分)可以避免不必要的重新下載和過度載入。
如何平衡?
通常,咱們不會完全禁止快取,而是透過版本號、雜湊、快取控制頭等方式來平衡效能和更新問題。這樣,瀏覽器在沒有必要時可以利用快取,而在需要時也能獲取最新的資源。