首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

JSP 自定義標籤開發完全指南:從 Tag 接口到 SimpleTag 實戰

     編輯:Java 綜合教學

自定義標籤核心價值

JSP 自定義標籤將 Java 代碼從頁面剝離,封裝為可復用的 XML 風格標籤,實現邏輯與展現分離,顯著提升頁面可維護性與團隊協作效率。

標籤處理器核心接口體系

標籤接口繼承關係圖

  • Tag:最基礎接口,定義生命周期方法
  • IterationTag:繼承 Tag,新增 doAfterBody() 支援迭代
  • BodyTag:繼承 IterationTag,新增 setBodyContent() 操作標籤體內容
  • TagSupport:Tag 默認實現,簡化開發
  • BodyTagSupport:BodyTag 默認實現
  • SimpleTag:JSP 2.0 新增,更簡潔的標籤開發模型
  • SimpleTagSupport:SimpleTag 默認實現

階梯式實戰:從簡單到複雜

第一層:最簡單標籤 - 實現 Tag 接口

輸出固定文本「Hello Tag」

public class HelloTag implements Tag {
    private PageContext pageContext;
    private Tag parent;

    public void setPageContext(PageContext pc) { this.pageContext = pc; }
    public void setParent(Tag t) { this.parent = t; }
    public Tag getParent() { return parent; }

    public int doStartTag() throws JspException { 
        return SKIP_BODY; // 無標籤體內容
    }

    public int doEndTag() throws JspException {
        try {
            pageContext.getOut().print("Hello Tag");
        } catch (IOException e) { throw new JspException(e); }
        return SKIP_PAGE;
    }

    public void release() { /* 釋放資源 */ }
}

TLD 配置 (/WEB-INF/tlds/CustomTaglib.tld):

<tag>
    <name>hello</name>
    <tag-class>com.lp.tags.HelloTag</tag-class>
    <body-content>empty</body-content> <!-- 無標籤體 -->
</tag>

JSP 使用:

<%@ taglib uri="/WEB-INF/tlds/CustomTaglib.tld" prefix="hello" %>
<hello:hello></hello:hello>

運行結果

第二層:帶屬性標籤 - 繼承 TagSupport

實現加法計算器 <add:add num1="3" num2="5"></add:add> 輸出 8

public class AddTag extends TagSupport {
    private int num1, num2;
    // Getter/Setter 必須提供,容器自動注入屬性值
    public void setNum1(int num1) { this.num1 = num1; }
    public void setNum2(int num2) { this.num2 = num2; }

    public int doEndTag() throws JspException {
        try {
            pageContext.getOut().print(num1 + num2);
        } catch (IOException e) { throw new JspException(e); }
        return EVAL_PAGE; // 繼續執行頁面其餘部分
    }
}

TLD 屬性聲明:

<tag>
    <name>add</name>
    <tag-class>com.lp.tags.AddTag</tag-class>
    <attribute>
        <name>num1</name>
        <required>true</required>
        <rtexprvalue>true</rtexprvalue> <!-- 支援運行時表達式 -->
    </attribute>
    <attribute>
        <name>num2</name>
        <required>true</required>
        <rtexprvalue>true</rtexprvalue>
    </attribute>
</tag>

加法標籤結果

第三層:帶標籤體 + 父子協作 - Switch/Case/Default

模擬 Java switch-case-default 邏輯,展示父子標籤通信機制

父標籤 SwitchTag:維護子標籤執行狀態標記

public class SwitchTag extends TagSupport {
    private boolean childExecuted = false; // 是否有子標籤已匹配

    public int doStartTag() { 
        childExecuted = false; 
        return EVAL_BODY_INCLUDE; // 執行標籤體(即 case/default 標籤)
    }

    // 供子標籤調用:檢查是否可執行
    public synchronized boolean canExecute() { return !childExecuted; }
    // 供子標籤調用:宣告已有匹配
    public synchronized void markExecuted() { childExecuted = true; }

    public void release() { childExecuted = false; }
}

子標籤 CaseTag:條件滿足時執行體並通知父標籤

public class CaseTag extends TagSupport {
    private boolean condition;
    public void setCondition(boolean condition) { this.condition = condition; }

    public int doStartTag() throws JspException {
        SwitchTag parent = (SwitchTag) getParent();
        if (!parent.canExecute()) return SKIP_BODY; // 已有匹配,跳過
        
        if (condition) {
            parent.markExecuted(); // 宣告匹配成功
            return EVAL_BODY_INCLUDE; // 執行標籤體
        }
        return SKIP_BODY; // 條件不滿足,跳過
    }
    public void release() { condition = false; }
}

子標籤 DefaultTag:無條件執行,但前提是無 case 匹配

public class DefaultTag extends TagSupport {
    public int doStartTag() throws JspException {
        SwitchTag parent = (SwitchTag) getParent();
        if (!parent.canExecute()) return SKIP_BODY;
        parent.markExecuted();
        return EVAL_BODY_INCLUDE;
    }
}

TLD 配置(注意 body-content>jsp</body-content> 支援標籤體內 JSP 腳本):

<tag><name>switch</name><tag-class>com.lp.tags.SwitchTag</tag-class><body-content>jsp</body-content></tag>
<tag><name>case</name><tag-class>com.lp.tags.CaseTag</tag-class><body-content>jsp</body-content>
    <attribute><name>cond</name><required>true</required><rtexprvalue>true</rtexprvalue></attribute>
</tag>
<tag><name>default</name><tag-class>com.lp.tags.DefaultTag</tag-class><body-content>jsp</body-content></tag>

JSP 使用:

<mytag:switch>
    <mytag:case cond='<%=userName.equals("zhangsan")%>'><%out.print("張三");%></mytag:case>
    <mytag:case cond='<%=userName.equals("lisi")%>'><%out.print("李四");%></mytag:case>
    <mytag:case cond='<%=userName.equals("wangwu")%>'><%out.print("王五");%></mytag:case>
    <mytag:default><%out.print("無");%></mytag:default>
</mytag:switch>

Switch 標籤結果

第四層:迭代標籤 - 實現 IterationTag

實現 <iterate:iterate items="${userList}" var="user"> 遍歷集合輸出表格

public class IterateTag extends TagSupport {
    private Iterator<?> iterator;
    private String var; // 頁面變量名
    private Object currentItem;

    public void setItems(Collection<?> coll) { 
        if (coll != null && !coll.isEmpty()) iterator = coll.iterator(); 
    }
    public void setVar(String var) { this.var = var; }

    public int doStartTag() throws JspException {
        if (iterator == null || !iterator.hasNext()) return SKIP_BODY;
        currentItem = iterator.next();
        exposeToPage(); // 存入 pageContext
        return EVAL_BODY_INCLUDE;
    }

    public int doAfterBody() throws JspException {
        if (iterator.hasNext()) {
            currentItem = iterator.next();
            exposeToPage();
            return EVAL_BODY_AGAIN; // 重新執行標籤體
        }
        return SKIP_BODY; // 結束迭代
    }

    private void exposeToPage() {
        if (currentItem == null) pageContext.removeAttribute(var, PageContext.PAGE_SCOPE);
        else pageContext.setAttribute(var, currentItem, PageContext.PAGE_SCOPE);
    }
    public void release() { iterator = null; currentItem = null; }
}

JSP 使用:

<% ArrayList<UserBean> list = ...; %>
<table>
    <tr><td>用戶名</td><td>年齡</td><td>郵箱</td></tr>
    <iterate:iterate items="<%=list%>" var="user">
        <jsp:useBean id="user" class="com.lp.beans.UserBean"/>
        <tr>
            <td><jsp:getProperty property="userName" name="user"/></td>
            <td>${user.age}</td>
            <td>${user["email"]}</td>
        </tr>
    </iterate:iterate>
</table>

迭代標籤結果

JSP 2.0 SimpleTag:更簡潔的開發模式

核心方法:

  • setJspContext(JspContext):設置上下文(PageContext 父類)
  • setParent(JspTag) / getParent():父子關係
  • setJspBody(JspFragment):接收標籤體片段,可多次調用 invoke()
  • doTag():單一入口處理所有邏輯,替代 doStartTag/doEndTag/doAfterBody
public class WelcomeSimpleTag extends SimpleTagSupport {
    private String name;
    private JspFragment body;

    public void setName(String name) { this.name = name; }
    public void setJspBody(JspFragment body) { this.body = body; }

    public void doTag() throws JspException, IOException {
        JspWriter out = getJspContext().getOut();
        out.print("Welcome, " + name + "! ");
        if (body != null) body.invoke(null); // 執行標籤體
    }
}

開發選型建議

場景推薦方案理由
簡單輸出、工具標籤SimpleTagSupport代碼最少,單一 doTag() 入口
需操作標籤體內容(如模板替換)BodyTagSupport可獲取 BodyContent 讀寫標籤體
複雜迭代、條件控制IterationTag / TagSupport精細控制 doStartTag/doAfterBody/doEndTag
父子標籤協作TagSupport 系統getParent() 類型轉換調用父方法
現有 Tag 遷移保持原模式避免重構風險

最佳實踐總結

  1. TLD 文件放在 /WEB-INF/tlds/,防止直接訪問
  2. 屬性名稱駝峰命名,rtexprvalue=true 支援 EL 表達式
  3. body-content 正確選擇:empty/scriptless/tagdependent/jsp
  4. release() 必須重置狀態,標籤處理器可被容器池化復用
  5. 同步控制:多線程環境下共享狀態需 synchronized
  6. 優先考慮 JSTL 標準標籤庫(c:forEach、c:if、fmt:formatNumber 等),僅在無法滿足時自定義
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved