JSP 標籤庫是將 Java 邏輯封裝為可重用標記的標準機制,讓頁面作者以宣告式標籤替代 Scriptlet。本文系統解析標籤庫四大核心組件、TLD 結構、標籤處理器生命週期、SimpleTag vs Classic Tag、BodyTag 緩衝機制、TagExtraInfo (TEI)、標籤檔 現代替代方案,並提供最佳實務建議。
| 組件 | 角色 | 形式 | 載入位置 |
|---|---|---|---|
| 標籤處理器 | 實作標籤行為的 Java 類別 | .class (實作 Tag/SimpleTag/BodyTag) | `WEB-INF/classes/` 或 `WEB-INF/lib/*.jar` |
| 標籤庫描述器 (TLD) | XML 元資料,描述標籤名稱、屬性、體內容、處理器類別 | .tld (XML) | `WEB-INF/tld/` 或 JAR 內 `META-INF/tld/` |
| 標籤附加資訊 (TEI) | 提供編譯期驗證、變數宣告資訊 (可選) | .class (實作 TagExtraInfo) | 同標籤處理器 |
| 標籤檔 | 純 JSP 片段實作標籤 (JSP 2.0+),無需 Java 類別/TLD | .tag | `WEB-INF/tags/` |
<?xml version="1.0" encoding="UTF-8"?>
<taglib xmlns="https://java.sun.com/xml/ns/javaee"
version="2.1">
<!-- 整個庫的元資料 -->
<tlib-version>1.2</tlib-version>
<jsp-version>2.1</jsp-version>
<short-name>my</short-name> <!-- 預設前綴 -->
<uri>https://mycompany.com/tags</uri> <!-- 唯一識別 URI -->
<description>企業通用標籤庫</description>
<!-- 單一標籤定義 -->
<tag>
<name>formatDate</name> <!-- 標籤名稱 -->
<tag-class>com.my.tags.FormatDateTag</tag-class>
<tei-class>com.my.tags.FormatDateTagTEI</tei-class> <!-- 可選 -->
<body-content>scriptless</body-content> <!-- empty | scriptless | tagdependent | JSP -->
<description>格式化日期輸出</description>
<attribute> <!-- 屬性定義 -->
<name>pattern</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue> <!-- 支援 EL/運算式 -->
<type>java.lang.String</type>
<description>SimpleDateFormat 模式</description>
</attribute>
<attribute>
<name>date</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
<type>java.util.Date</type>
</attribute>
<variable> <!-- 宣告變數 (供 TEI 使用) -->
<name-given>formattedDate</name-given>
<variable-class>java.lang.String</variable-class>
<declare>true</declare>
<scope>NESTED</scope> <!-- AT_BEGIN | AT_END | NESTED -->
</variable>
</tag>
</taglib>
| 值 | 行為 | 適用場景 |
|---|---|---|
empty | 標籤體必須為空 ` | 無需內容的功能性標籤 (如 ` |
scriptless | 允許 EL、標準/自訂標籤、模板文字,**禁止** Scriptlet (`<% %>`)、宣告 (`<%! %>`) | **最常用**,現代 JSP 最佳實務 |
tagdependent | 標籤體視為純文字,不解析 EL/標籤,原樣傳給處理器 | SQL、JavaScript、樣板語言、原始碼輸出 |
JSP | 完整 JSP 語法 (含 Scriptlet) — **不推薦** | 舊版相容 |
TEI 類別實作 `javax.servlet.jsp.tagext.TagExtraInfo`,提供兩個關鍵方法:
public class MyTagTEI extends TagExtraInfo {
// 1. 驗證屬性 (編譯期)
@Override
public boolean isValid(TagData data) {
// data.getAttribute("attrName") 取得屬性值 (可能為 TagData.REQUEST_TIME_VALUE)
String val = data.getAttributeString("count");
if (val != null && !val.equals(TagData.REQUEST_TIME_VALUE)) {
try { Integer.parseInt(val); return true; }
catch (NumberFormatException e) { return false; }
}
return true; // 運算式值無法驗證,假設合法
}
// 2. 宣告變數資訊 (供容器產生程式碼)
@Override
public VariableInfo[] getVariableInfo(TagData data) {
return new VariableInfo[] {
new VariableInfo("loopVar", "java.lang.String", true, VariableInfo.NESTED)
};
}
}
TLD 中 `
將標籤邏輯寫為 `.tag` 檔案放入 `WEB-INF/tags/` (或子目錄對應前綴命名空間)。
<%@ tag language="java" pageEncoding="UTF-8" %>
<%@ attribute name="date" required="true" type="java.util.Date" rtexprvalue="true" %>
<%@ attribute name="pattern" required="true" type="java.lang.String" rtexprvalue="true" %>
<%@ variable name-given="formattedDate" variable-class="java.lang.String" scope="NESTED" %>
<%@ taglib prefix="fmt" uri="https://java.sun.com/jsp/jstl/fmt" %>
<fmt:formatDate value="${date}" pattern="${pattern}" var="formattedDate" scope="NESTED" />
<%@ taglib prefix="my" tagdir="/WEB-INF/tags" %>
<my:formatDate date="${article.publishDate}" pattern="yyyy-MM-dd" />
格式化後: ${formattedDate}
<%@ taglib prefix="c" uri="https://java.sun.com/jsp/jstl/core" %> <!-- 標準庫,容器內建 -->
<%@ taglib prefix="my" uri="https://mycompany.com/tags" %> <!-- 自訂庫,需 TLD -->
<%@ taglib prefix="my" tagdir="/WEB-INF/tags" %> <!-- 標籤檔目錄 -->
標籤庫本質是**伺服器端元件化**。現代前端框架 (React/Vue/Svelte/Astro) 以**客戶端元件**取代:
- `
- `
- `
理解標籤庫生命週期、範圍、體內容處理,有助於設計對應的前端元件 Props、Slots、Context 模型。