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

JSP 自定義標籤開發全解析:從入門到精通的完整指南

     編輯:Java 相關知識

前言:為什麼需要自定義標籤

JSP 早期大量 Scriptlet (<% ... %>) 導致頁面邏輯與展示耦合,難以維護。自定義標籤 (Custom Tag) 將 Java 邏輯封裝為類 XML 標籤,實現:

  • 關注點分離:頁面只負責展示,業務邏輯在 Tag Handler
  • 復用性:跨項目共享標籤庫 (JAR)
  • 國際化友好:標籤屬性動態輸出多語言文本
  • 前端友好:設計師可像寫 HTML 一樣使用標籤

核心概念速覽

術語說明
Tag (標籤)XML 風格元素,如 <my:pagination total="100" />,大小寫敏感
Tag Library (標籤庫)功能相關標籤的集合,打包為 JAR 發布
TLD (Tag Library Descriptor)XML 配置文件,定義標籤名、處理類、屬性、Body 類型等元數據
Tag Handler (標籤處理類)實現標籤邏輯的 Java 類,繼承 TagSupport 或實現 SimpleTag
PageContextJSP 隱含對象,提供對 request/session/application/out 等作用域訪問

標籤庫引用與使用

1. JSP 頁面引入標籤庫

<%@ taglib prefix="my" uri="/WEB-INF/tlds/mytags.tld" %>
<!-- 或使用 urn 風格 uri(需 web.xml 映射) -->
<%@ taglib prefix="my" uri="https://mycompany.com/tags" %>

2. 使用標籤

<!-- 空標籤 -->
<my:copyright year="2024" />

<!-- 帶 Body 標籤 -->
<my:panel title="用戶列表">
    <table>...</table>
</my:panel>

容器處理流程(底層原理)

  1. JSP 編譯期遇到 <%@ taglib ...%>,記錄 prefix → uri 映射
  2. 運行期遇到 <my:panel>,根據 prefix 找到 uri
  3. 在 web.xml 中查找 <taglib> 元素匹配 uri,獲取 TLD 文件路徑
  4. 解析 TLD,根據標籤名 panel 找到 <tag-class>
  5. 實例化 Tag Handler,調用 setPageContext()、setParent() 注入上下文
  6. 調用屬性 Setter 方法注入標籤屬性值
  7. 調用 doStartTag() → 處理 Body → 調用 doEndTag()
  8. 根據返回值決定頁面繼續執行流程

開發標準標籤(基於 TagSupport)

1. TagHandler 核心類結構

package com.example.tags;

import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspWriter;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.tagext.TagSupport;
import java.io.IOException;
import java.time.format.DateTimeFormatter;
import java.time.LocalDateTime;

public class CurrentTimeTag extends TagSupport {
    private String pattern = "yyyy-MM-dd HH:mm:ss"; // 默認格式
    
    // 屬性 Setter(容器自動調用)
    public void setPattern(String pattern) {
        this.pattern = pattern;
    }
    
    @Override
    public int doStartTag() throws JspException {
        try {
            JspWriter out = pageContext.getOut();
            String formatted = LocalDateTime.now().format(DateTimeFormatter.ofPattern(pattern));
            out.print(formatted);
        } catch (IOException e) {
            throw new JspException("寫入響應失敗", e);
        }
        return SKIP_BODY; // 無 Body 內容
    }
    
    @Override
    public int doEndTag() throws JspException {
        return EVAL_PAGE; // 繼續執行頁面其餘部分
    }
}

2. TLD 文件定義 (/WEB-INF/tlds/mytags.tld)

<?xml version="1.0" encoding="UTF-8"?>
<taglib xmlns="https://java.sun.com/xml/ns/javaee"
        xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="https://java.sun.com/xml/ns/javaee https://java.sun.com/xml/ns/javaee/web-jsptaglibrary_2_1.xsd"
        version="2.1">
    
    <tlib-version>1.0</tlib-version>
    <short-name>my</short-name>
    <uri>https://mycompany.com/tags</uri>
    
    <tag>
        <name>currentTime</name>
        <tag-class>com.example.tags.CurrentTimeTag</tag-class>
        <body-content>empty</body-content>
        <attribute>
            <name>pattern</name>
            <required>false</required>
            <rtexprvalue>true</rtexprvalue> <!-- 支援 <%= ... %> 動態表達式 -->
            <type>java.lang.String</type>
            <description>日期時間格式模式</description>
        </attribute>
    </tag>
    
</taglib>

3. web.xml 映射(可選,若 uri 使用 urn 風格)

<taglib>
    <taglib-uri>https://mycompany.com/tags</taglib-uri>
    <taglib-location>/WEB-INF/tlds/mytags.tld</taglib-location>
</taglib>

進階標籤類型與 Body 處理

Body Content 類型TLD 聲明適用場景處理方式
empty<body-content>empty</body-content>無體標籤 <tag/>doStartTag() 返回 SKIP_BODY
scriptless (JSP 2.0+)<body-content>scriptless</body-content>Body 含 EL/標籤/模板文本,無 Scriptlet推薦使用 SimpleTagSupport,getJspBody().invoke(out)
tagdependent<body-content>tagdependent</body-content>Body 為純文本/模板,由標籤自行解析 (如 SQL、代碼生成)doStartTag() 返回 EVAL_BODY_INCLUDE,在 doEndTag() 讀取 bodyContent.getString()
JSP (舊規範)<body-content>JSP</body-content>Body 可含 Scriptlet(不推薦)同 scriptless

SimpleTagSupport:現代標籤開發首選 (JSP 2.0+)

更簡潔、無 doStartTag/doEndTag 狀態機、自然支援 Body 處理、支援標籤屬性依賴注入風格。

public class PanelTag extends SimpleTagSupport {
    private String title;
    private String cssClass = "panel panel-default";
    
    public void setTitle(String title) { this.title = title; }
    public void setCssClass(String cssClass) { this.cssClass = cssClass; }
    
    @Override
    public void doTag() throws JspException, IOException {
        JspWriter out = getJspContext().getOut();
        
        // 渲染面板頭部
        out.write(String.format("<div class=\"%s\"><div class=\"panel-heading\">%s</div><div class=\"panel-body\">", 
            cssClass, title));
        
        // 渲染 Body 內容(支援 EL、其他標籤)
        getJspBody().invoke(out);
        
        // 渲染面板尾部
        out.write("</div></div>");
    }
}

TLD 中聲明:<body-content>scriptless</body-content>

標籤屬性進階特性

  • rtexprvalue="true":屬性值可為運行時表達式 (<%= user.name %>、${user.name})
  • type:指定屬性 Java 類型,容器自動類型轉換
  • required="true":強制屬性,缺失編譯報錯
  • deferredValue / deferredMethod:支援 EL 延遲求值 (JSP 2.1+)

標籤文件:零 Java 代碼開發標籤

將常用片段封裝為 .tag 文件,放在 /WEB-INF/tags/ 目錄:

<!-- /WEB-INF/tags/copyright.tag -->
<%@ tag description="版權標籤" pageEncoding="UTF-8" %>
<%@ attribute name="company" required="true" type="java.lang.String" %>
<%@ attribute name="year" required="false" type="java.lang.Integer" %>
<div class="copyright">
    © ${year != null ? year : (new java.util.Date()).year + 1900} ${company}. All rights reserved.
</div>

使用:<%@ taglib prefix="t" tagdir="/WEB-INF/tags" %> <t:copyright company="MyCorp" />

國際化整合實戰

在 TagHandler 中通過 pageContext.getRequest().getLocale() 獲取用戶 Locale,配合 ResourceBundle 輸出多語言:

public class I18nMessageTag extends SimpleTagSupport {
    private String key;
    public void setKey(String key) { this.key = key; }
    
    @Override
    public void doTag() throws JspException, IOException {
        Locale locale = getJspContext().getRequest().getLocale();
        ResourceBundle bundle = ResourceBundle.getBundle("messages", locale);
        String msg = bundle.getString(key);
        getJspContext().getOut().write(msg);
    }
}

最佳實踐與避坑指南

場景建議
大量 HTML 輸出使用 Tag File 或模板引擎,避免在 Java 中拼接字符串
需要訪問請求參數優先用 pageContext.getRequest().getParameter(),而非標籤屬性傳遞
標籤巢狀用 findAncestorWithClass(this, ParentTag.class) 獲取父標籤上下文
性能敏感標籤類做成無狀態、線程安全;避免在 doTag 中創建重對象
異常處理拋出 JspException 讓容器統一處理,不要吞噬異常

總結

自定義標籤是 JSP 實現「模板化、組件化、可復用」的核心機制。掌握 TagSupport (經典) 與 SimpleTagSupport (現代) 兩種開發模式、TLD 元數據定義、Body 內容類型 選擇、Tag File 快速開發,即可構建企業級標籤庫。現代項目中,配合前端模板引擎或 React/Vue 組件化,JSP 標籤多用於服務端片段復用與舊系統改造。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved