傳統 JSP 緩存(如 OSCache、Ehcache 整合)多針對整頁或數據對象,難以滿足「頁面大部分靜態、少量動態個性化」的場景。JSP 2.0 引入的 EL(Expression Language)與標簽庫機制,使得片段級緩存 + 動態表達式運行時求值 成為可能。本文剖析一套基於 SimpleTagSupport 的輕量級緩存標簽庫實現原理。
<jc:cache> + <jc:dynamic> 雙標簽協作<%@ taglib prefix="jc" uri="https://devsphere.com/jspcache" %>
<jc:cache id="productListFragment" scope="application">
<!-- 這裡的內容只會執行一次,產出的 HTML 存入 application scope -->
<table>
<c:forEach items="${productService.latestProducts}" var="p">
<tr><td>${p.name}</td><td>${p.price}</td></tr>
</c:forEach>
</table>
<!-- 動態插入:當前用戶購物車數量 -->
<jc:dynamic expr="sessionScope.cart.itemCount" />
</jc:cache>
| 類 | 繼承/實現 | 核心邏輯 |
|---|---|---|
CacheTag | SimpleTagSupport | 1. 檢查緩存開關(web.xml init-param) 2. 從指定 scope 獲取緩存字符串,無則執行 body 並捕獲輸出到 StringWriter 存入 scope 3. 調用 JspUtils.eval() 對緩存字符串中的 ${...} 進行 EL 求值4. 輸出求值結果 |
DynamicTag | SimpleTagSupport | 1. 構造完整 EL 表達式 ${expr}2. 向上查找最近的 CacheTag 祖先3. 若緩存啟用且找到祖先:直接輸出「未求值的表達式字符串」供祖先統一求值 4. 否則:立即調用 JspUtils.eval() 求值並輸出 |
JspUtils | 工具類 | 封裝 ExpressionEvaluator,提供 eval(String expr, Class type, JspContext ctx) 統一入口,兼容無 EL 表達式的純文本直通。 |
CacheTag.doTag() → body 執行 → 輸出含 ${sessionScope.cart.itemCount} 的 HTML 字符串 → 存入 application scope。CacheTag.doTag() → 讀取緩存字符串 → JspUtils.eval() 以當前 PageContext 為上下文求值 → ${sessionScope.cart.itemCount} 解析為當前用戶實際數量 → 輸出最終 HTML。DynamicTag 僅佔位,不直接輸出值,確保「緩存片段靜態化、動態部分延遲綁定」。application.removeAttribute("productListFragment"),配合後台管理「發布商品」操作調用。ScheduledExecutorService 定期清理指定前綴屬性。productListFragment_v${productVersion},數據變更遞增版本號自動產生新緩存鍵。| 優點 | 缺點/限制 |
|---|---|
| 零依賴,僅需 Servlet/JSP 容器 | 緩存對象為字符串,大片段佔用 Heap,需配合 scope="session" 或外部緩存(Redis)應對大流量 |
| 動態表達式靈活,支持任意 EL | 無內建 LRU/TTL 淘汰,需自行實現或引入成熟框架 |
開發期可關閉緩存(enabled=false)即時見效 | 集群環境下 application scope 不共享,需改用分佈式緩存 Key |