JSTL (JSP Standard Tag Library) 是 Java Web 開發中替代 Scriptlet 的標準方案,配合 EL 表達式實現視圖層邏輯與展示分離。本文系統講解 JSTL 核心標簽庫、EL 函數庫、自定義 EL 函數、自定義標簽 (SimpleTag) 開發全流程。
<%@ taglib prefix="c" uri="https://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="https://java.sun.com/jsp/jstl/functions" %>
<%@ taglib prefix="fmt" uri="https://java.sun.com/jsp/jstl/fmt" %>
<%@ taglib prefix="sql" uri="https://java.sun.com/jsp/jstl/sql" %>
<%@ taglib prefix="x" uri="https://java.sun.com/jsp/jstl/xml" %>
| 標簽 | 功能 | 關鍵屬性 |
|---|---|---|
| <c:out> | 輸出值,支持 HTML 轉義 | value, default, escapeXml |
| <c:set> | 設置變量/屬性 | var, value, scope, target, property |
| <c:remove> | 移除變量 | var, scope |
| <c:catch> | 捕獲異常 | var (異常對象) |
| <c:if> | 條件判斷 | test, var, scope |
| <c:choose> <c:when> <c:otherwise> | 多條件分支 | test |
| <c:forEach> | 集合/數組/範圍迭代 | items, var, begin, end, step, varStatus |
| <c:forTokens> | 字符串分割迭代 | items, delims, var |
| <c:import> | 包含頁面/遠程資源 | url, context, var, scope |
| <c:url> | 構建 URL (支持會話追蹤) | value, var, scope, context |
| <c:redirect> | 重定向 | url, context |
| <c:param> | URL 參數 (自動編碼) | name, value |
<c:forEach items="${list}" var="item" varStatus="status">
索引: ${status.index} (從0開始)
計數: ${status.count} (從1開始)
首項: ${status.first}
末項: ${status.last}
當前對象: ${item}
</c:forEach>
<fmt:setLocale value="zh_CN" scope="session"/>
<fmt:setBundle basename="messages" var="msg"/>
<fmt:message key="welcome" bundle="${msg}"/>
<fmt:formatNumber value="12345.6" type="currency" currencySymbol="¥"/>
<fmt:formatDate value="${now}" pattern="yyyy-MM-dd HH:mm:ss"/>
| 函數 | 說明 | 示例 |
|---|---|---|
| toLowerCase(s) | 轉小寫 | ${fn:toLowerCase('ABC')} → abc |
| toUpperCase(s) | 轉大寫 | ${fn:toUpperCase('abc')} → ABC |
| trim(s) | 去首尾空格 | ${fn:trim(' hi ')} → hi |
| length(obj) | 集合/數組/字符串長度 | ${fn:length(list)} → 5 |
| split(s, delim) | 分割字符串為數組 | ${fn:split('a,b,c', ',')[1]} → b |
| join(arr, delim) | 數組連接為字符串 | ${fn:join(arr, '-')} → a-b-c |
| indexOf(s, sub) | 子串首次索引 | ${fn:indexOf('hello', 'el')} → 1 |
| contains(s, sub) | 包含判斷 (大小寫敏感) | ${fn:contains('Hello', 'ell')} → true |
| containsIgnoreCase(s, sub) | 忽略大小寫包含 | ${fn:containsIgnoreCase('Hello', 'ELL')} → true |
| startsWith(s, prefix) | 前綴判斷 | ${fn:startsWith('hello', 'he')} → true |
| endsWith(s, suffix) | 後綴判斷 | ${fn:endsWith('hello', 'lo')} → true |
| replace(s, before, after) | 替換 | ${fn:replace('a b c', ' ', '-')} → a-b-c |
| substring(s, begin, end) | 截取子串 | ${fn:substring('hello', 1, 4)} → ell |
| substringAfter(s, sub) | 子串後內容 | ${fn:substringAfter('a.b.c', '.')} → b.c |
| substringBefore(s, sub) | 子串前內容 | ${fn:substringBefore('a.b.c', '.')} → a |
| escapeXml(s) | XML/HTML 轉義 | ${fn:escapeXml(' ')} → <div> |
package com.example.el;
public class CustomFunctions {
public static String formatMoney(double amount) {
return String.format("¥%,.2f", amount);
}
public static String maskPhone(String phone) {
if (phone == null || phone.length() < 7) return phone;
return phone.substring(0, 3) + "****" + phone.substring(7);
}
}
<?xml version="1.0" encoding="UTF-8"?>
<taglib version="2.0"
xmlns="https://java.sun.com/xml/ns/j2ee"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://java.sun.com/xml/ns/j2ee
https://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd">
<tlib-version>1.0</tlib-version>
<short-name>customFn</short-name>
<uri>https://example.com/custom-fn</uri>
<function>
<name>formatMoney</name>
<function-class>com.example.el.CustomFunctions</function-class>
<function-signature>java.lang.String formatMoney(double)</function-signature>
</function>
<function>
<name>maskPhone</name>
<function-class>com.example.el.CustomFunctions</function-class>
<function-signature>java.lang.String maskPhone(java.lang.String)</function-signature>
</function>
</taglib>
<%@ taglib prefix="myfn" uri="https://example.com/custom-fn" %>
價格: ${myfn:formatMoney(12345.6)} <!-- ¥12,345.60 -->
電話: ${myfn:maskPhone('13812345678')} <!-- 138****5678 -->
SimpleTag (介面)
↑
SimpleTagSupport (抽象基類, 推薦繼承)
↑
TagAdapter (適配舊式 Tag 介面)
setJspContext(JspContext):注入 pageContextsetParent(Tag):設置父標簽setJspBody(JspFragment):注入標簽體片段doTag():核心邏輯入口,此處輸出、迭代、操作標簽體// 執行標簽體內容,輸出到指定 Writer
void invoke(Writer out) throws JspException, IOException;
// 獲取調用頁面的 JspContext (即 pageContext)
JspContext getJspContext();
| 類型 | 說明 | SimpleTag 支持 |
|---|---|---|
| empty | 無標簽體 | ✅ |
| scriptless | 無 Scriptlet,支持 EL/JSTL/HTML | ✅ (推薦) |
| tagdependent | 原文本,容器不解析,由標簽類自行處理 | ✅ |
| JSP | 完整 JSP 語法 (含 Scriptlet) | ❌ 不支持 |
public class HelloTag extends SimpleTagSupport {
private JspContext jspContext;
@Override
public void setJspContext(JspContext ctx) {
this.jspContext = ctx;
}
@Override
public void doTag() throws JspException, IOException {
jspContext.getOut().write("Hello Custom Tag!");
}
}
public class UpperTag extends SimpleTagSupport {
private JspContext jspContext;
@Override
public void setJspContext(JspContext ctx) { this.jspContext = ctx; }
@Override
public void doTag() throws JspException, IOException {
JspFragment body = getJspBody();
if (body != null) {
StringWriter sw = new StringWriter();
body.invoke(sw); // 捕獲標簽體輸出
String content = sw.toString().toUpperCase();
jspContext.getOut().write(content);
}
}
}
public class IfTag extends SimpleTagSupport {
private boolean test;
private JspContext jspContext;
public void setTest(boolean test) { this.test = test; } // 屬性 setter
@Override
public void setJspContext(JspContext ctx) { this.jspContext = ctx; }
@Override
public void doTag() throws JspException, IOException {
if (test) {
getJspBody().invoke(null); // 直接輸出到頁面
}
}
}
<?xml version="1.0" encoding="UTF-8"?>
<taglib version="2.0" ...>
<tlib-version>1.0</tlib-version>
<short-name>mytag</short-name>
<uri>https://example.com/custom-tag</uri>
<tag>
<name>hello</name>
<tag-class>com.example.tag.HelloTag</tag-class>
<body-content>empty</body-content>
</tag>
<tag>
<name>upper</name>
<tag-class>com.example.tag.UpperTag</tag-class>
<body-content>scriptless</body-content>
</tag>
<tag>
<name>if</name>
<tag-class>com.example.tag.IfTag</tag-class>
<body-content>scriptless</body-content>
<attribute>
<name>test</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue> <!-- 支持 EL 表達式 -->
<type>boolean</type>
</attribute>
</tag>
</taglib>
<%@ taglib prefix="my" uri="https://example.com/custom-tag" %>
<%@ taglib prefix="c" uri="https://java.sun.com/jsp/jstl/core" %>
<my:hello/> <!-- Hello Custom Tag! -->
<my:upper>
這段內容將被轉為大寫輸出
</my:upper>
<c:set var="score" value="85"/>
<my:if test="${score >= 60}">
恭喜通過!
</my:if>
/WEB-INF/tld/ 或子目錄 (不在 classes/lib 下)https://com.example/project/taglibSkipPageException 可終止頁面後續執行getParent() 獲取父標簽,協作共享狀態JSTL + EL + 自定義標簽構成了 JSP 視圖層的「三板斧」:
掌握 SimpleTagSupport 生命週期、JspFragment.invoke() 標簽體控制、body-content 類型選擇、TLD 屬性聲明,即可開發出專業級標簽庫,徹底消除 JSP 中的 Java 代碼,實現真正的視圖分離。