JSTL (JSP Standard Tag Library) 與 EL (Expression Language) 是 JSP 頁面邏輯與展現分離的基石。本文系統整理核心標簽庫 (core)、EL 語法與隱性對象、常見陷阱,助你編寫乾淨、可維護的視圖層代碼。
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<!-- 標準實現需額外引入 (Tomcat 內建) -->
<dependency>
<groupId>taglibs</groupId>
<artifactId>standard</artifactId>
<version>1.1.2</version>
</dependency>
JSP 頁面引入核心庫:
<%@ taglib prefix="c" uri="https://java.sun.com/jsp/jstl/core" %>
| 標簽 | 功能 | 關鍵屬性 | 示例 |
|---|---|---|---|
<c:set> | 設置變量 (相當於 setAttribute) | var, value, scope, target, property | <c:set var="msg" value="Hello" /> |
<c:out> | 輸出值 (自動 HTML 转義,防 XSS) | value, default, escapeXml | <c:out value="${msg}" default="無" /> |
<c:remove> | 移除變量 | var, scope | <c:remove var="msg" /> |
<c:if> | 條件判斷 (無 else) | test, var (存儲結果) | <c:if test="${user.admin}">...</c:if> |
<c:choose> / <c:when> / <c:otherwise> | 多條件分支 (switch-case-default) | test | 見下文示例 |
<c:catch> | 異常捕獲 (類 try-catch) | var (存儲異常對象) | <c:catch var="e">${1/0}</c:catch> |
<c:forEach> | 遍歷集合/數組/範圍 | items, var, begin, end, step, varStatus | <c:forEach items="${list}" var="item" varStatus="s">${s.index}</c:forEach> |
<c:forTokens> | 按分隔符分割字符串遍歷 | items, delims, var | <c:forTokens items="a,b,c" delims="," var="x">${x}</c:forTokens> |
<c:import> | 引入外部資源 (URL、文件) | url, var, scope, charEncoding | <c:import url="https://example.com" var="content" /> |
<c:redirect> | 重定向 | url, context | <c:redirect url="/login.jsp" /> |
<c:url> | 構建 URL (自動加 SessionID、編碼參數) | value, context, var | <c:url value="/search" var="u"><c:param name="q" value="${kw}" /></c:url> |
<c:param> | 為 url/redirect/import 添加參數 | name, value | 同上 |
<c:set var="score" value="85" />
<c:choose>
<c:when test="${score >= 90}">優秀</c:when>
<c:when test="${score >= 60}">及格</c:when>
<c:otherwise>不及格</c:otherwise>
</c:choose>
<c:catch var="exception">
<% int i = 5 / 0; %>
</c:catch>
<c:if test="${exception != null}">
捕獲到異常: ${exception.message}
</c:if>
| 屬性 | 說明 | |
|---|---|---|
index | 當前迭代索引 (0-based) | |
count | 當前迭代次數 (1-based) | |
first | 是否首次迭代 | |
| last | 是否最後一次迭代 | |
begin | 起始索引 | |
end | 結束索引 | |
step | 步長 |
${expression}:以 $ 開頭,大括號包裹;+ - * / div % mod)、邏輯 (&& and || or ! not)、關係 (== eq != ne < lt > gt <= le >= ge)、empty 運算符;empty obj:判斷 null、空字符串、空數組、空集合、空 Map → true。| 語法 | 適用場景 | 示例 |
|---|---|---|
${obj.property} | 屬性名固定、符合 Java 標識符規則 | ${user.name} |
${obj["property"]} | 屬性名動態、含特殊字符、或為數字索引 | ${map["key-name"]}, ${list[0]}, ${map[keyVar]} |
| 隱性對象 | 類型 | 對應範圍 | 示例 |
|---|---|---|---|
pageScope | Map | PageContext | ${pageScope.attr} |
requestScope | Map | HttpServletRequest | ${requestScope.id} |
sessionScope | Map | HttpSession | ${sessionScope.user.name} |
applicationScope | Map | ServletContext | ${applicationScope.config} |
param | Map | 請求參數 (單值) | ${param.id} 等同 request.getParameter("id") |
paramValues | Map | 請求參數 (多值) | ${paramValues.ids[0]} |
header | Map | 請求頭 (單值) | ${header["User-Agent"]} |
headerValues | Map | 請求頭 (多值) | - |
cookie | Map | Cookie 對象 | ${cookie.JSESSIONID.value} |
initParam | Map | 上下文初始化參數 | ${initParam.contextConfigLocation} |
pageContext | PageContext | JSP 隱性對象入口 | ${pageContext.request.contextPath} |
使用 ${name} 不指定作用域時,按順序查找:
pageScope → requestScope → sessionScope → applicationScope
找到即返回,否則返回空字符串 (不拋異常)。
EL 設計為只讀表達式,不能調用帶參方法、靜態方法、構造器。
解決方案:
① 自定義 EL 函數 (Tag Library Descriptor + 靜態方法);
② JSP 2.3+ / EL 3.0 支持 ${bean.method()} (需容器支持);
③ 控制器預計算放入作用域 (推薦,符合 MVC 分層)。
| 場景 | 問題 | 解決 |
|---|---|---|
| 防 XSS | 直接輸出 ${userInput} 含腳本 | 必用 <c:out value="${userInput}" /> (默認 escapeXml=true) |
| 空值顯示默認 | ${user.name} 為空顯示空白 | <c:out value="${user.name}" default="匿名用戶" /> |
| 動態屬性訪問 | 屬性名存變量中 | ${bean[propName]} 或 ${map[keyVar]} |
| 數字格式化 | EL 無內置格式化 | 用 fmt 標簽庫 <fmt:formatNumber> 或控制器預格式化 |
| 日期格式化 | 同上 | <fmt:formatDate value="${date}" pattern="yyyy-MM-dd" /> |
| 集合判空 | <c:if test="${not empty list}"> | empty 運算符覆蓋 null/空集合 |
JSTL + EL 實現了 JSP 的無腳本化:
✅ c:set/out/remove 管理變量生命週期;
✅ c:if/choose/catch 控制流程;
✅ c:forEach/forTokens 遍歷渲染;
✅ c:url/param/redirect/import 處理鏈接與資源;
✅ EL 隱性對象 精準訪問四大作用域與請求參數;
✅ empty、[] 動態訪問、default 空值處理。
堅持「控制器準備數據,視圖純渲染」,代碼即文檔,維護零負擔。