1. 程式人生 > >如何開發自定義標簽

如何開發自定義標簽

lin lns abcd tro lib case invoke java類 ext

一、簡介

原理:用戶自定義的 jsp 標記。當一個含有自定義標簽的 jsp 頁面被 jsp 引擎編譯成 servlet 時,tag 標簽被轉化成了對一個標簽處理器類的對象的操作。

標簽庫API:定義在 javax.servlet.jsp.tagext 中

技術分享

二、實現SimpleTag接口的標簽處理器類的生命周期

1)setJspContext:Jsp 引擎將代表 JSP 頁面的 pageContext 對象傳遞給標簽處理器對象

2)setParent:Jsp 引擎將父標簽處理器對象傳遞給當前標簽處理器對象。只有存在父標簽時,Jsp 引擎才會調用才方法

3)setXxx:設置標簽屬性。只有定義屬性才調用該方法。

4)setJspBody:若存在標簽體,Jsp 引擎將把標簽體封裝成一個 JspFragment 對象,調用 setJspBody 方法將 JSPFragment 對象傳遞給標簽處理器對象。若標簽體為空,這 setJspBody 將不會被 Jsp 引擎調用。

5)doTag:容器調用標簽處理器對象的 doTag 方法執行標簽邏輯

三、自定義標簽開發與應用步驟

  • 用Java類來實現標簽功能(類中的字段和屬性就是使用標簽時的屬性。就像 a 標簽的 href 屬性)
  • 用TLD文件描述標簽的名字、類型以及該標簽所關聯的java類等

1. 編寫完成標簽功能的 Java 類(標簽處理器)

HelloSimpleTag(實現SimpleTag接口):把 value 輸出 count 遍

public class HelloSimpleTag implements SimpleTag {

    private String value;
    private String count;
    
    public void setValue(String value) {
        this.value = value;
    }
    
    public void setCount(String count) {
        this.count = count;
    }
    
    //標簽體邏輯實際應該編寫到該方法中. 
    @Override
    
public void doTag() throws JspException, IOException { // System.out.println("value: " + value + ", count: " + count); // // HttpServletRequest request = (HttpServletRequest) pageContext.getRequest(); // pageContext.getOut().print("Hello: " + request.getParameter("name")); JspWriter out = pageContext.getOut(); int c = 0; c = Integer.parseInt(count); for(int i = 0; i < c; i++){ out.print((i + 1) + ": " + value); out.print("<br>"); } } @Override public JspTag getParent() { System.out.println("getParent"); return null; } @Override public void setJspBody(JspFragment arg0) { System.out.println("setJspBody"); } private PageContext pageContext; //JSP 引擎調用, 把代表 JSP 頁面的 PageContext 對象傳入 //PageContext 可以獲取 JSP 頁面的其他 8 個隱含對象. //所以凡是 JSP 頁面可以做的標簽處理器都可以完成. @Override public void setJspContext(JspContext arg0) { System.out.println(arg0 instanceof PageContext); this.pageContext = (PageContext) arg0; } @Override public void setParent(JspTag arg0) { System.out.println("setParent"); } }

ReadFileTag(繼承SimpleTagSupport類):給一個文本路徑,輸出該文本裏的內容

public class ReadFileTag extends SimpleTagSupport{

    //相對於當前 WEB 應用的根路徑的文件名
    private String src;

    public void setSrc(String src) {
        this.src = src;
    }
    
    @Override
    public void doTag() throws JspException, IOException {
        PageContext pageContext = (PageContext) getJspContext();
        InputStream in = pageContext.getServletContext().getResourceAsStream(src);
        BufferedReader reader = new BufferedReader(new InputStreamReader(in)); 
        
        String str = null;
        while((str = reader.readLine()) != null){
            str = Pattern.compile("<").matcher(str).replaceAll("&lt");
            str = Pattern.compile(">").matcher(str).replaceAll("&gt");
            
            pageContext.getOut().println(str);
            pageContext.getOut().println("<br>"); 
        }
    }
}

2. 編寫標簽庫描述(tld)文件,在tld文件中對自定義中進行描述

<?xml version="1.0" encoding="UTF-8" ?>

<taglib xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd"
    version="2.0">

    <!-- 描述 TLD 文件 -->
    <description>MyTag 1.0 core library</description>
    <display-name>MyTag core</display-name>
    <tlib-version>1.0</tlib-version>

    <!-- 建議在 JSP 頁面上使用的標簽的前綴 -->
    <short-name>mytag</short-name>
    <!-- 作為 tld 文件的 id, 用來唯一標識當前的 TLD 文件, 多個 tld 文件的 URI 不能重復. 通過 JSP 頁面的 taglib 
        標簽的 uri 屬性來引用. -->
    <uri>http://www.xxx.com/mytag/core</uri>

    <!-- 描述自定義的 HelloSimpleTag 標簽 -->
    <tag>
        <!-- 標簽的名字: 在 JSP 頁面上使用標簽時的名字 -->
        <name>hello</name>

        <!-- 標簽所在的全類名 -->
        <tag-class>com.zhang.javaweb.tag.HelloSimpleTag</tag-class>
        <!-- 標簽體的類型 -->
        <body-content>empty</body-content>

        <!-- 描述當前標簽的屬性 -->
        <attribute>
            <!-- 屬性名 -->
            <name>value</name>
            <!-- 該屬性是否被必須 -->
            <required>true</required>
            <!-- rtexprvalue: runtime expression value 當前屬性是否可以接受運行時表達式的動態值 -->
            <rtexprvalue>true</rtexprvalue>
        </attribute>

        <attribute>
            <name>count</name>
            <required>false</required>
            <rtexprvalue>false</rtexprvalue>
        </attribute>
    </tag>

<tag> <name>readerFile</name> <tag-class>com.zhang.javaweb.tag.ReadFileTag</tag-class> <body-content>empty</body-content> <attribute> <name>src</name> <required>true</required> <rtexprvalue>true</rtexprvalue> </attribute> </tag>
</taglib>

3. 在JSP頁面中導入和使用自定義標簽

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>

<!-- 導入標簽庫(描述文件) -->
<%@taglib uri="http://www.xxx.com/mytag/core" prefix="mytag" %>
    
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
    <mytag:readerFile src="/WEB-INF/note.txt" />
    
<!-- count不能接收運行時表達式的值,所以不能用el表達式 --> <mytag:hello value="${param.name }" count="10" /> </body> </html>

三、使用方法總結

1. 帶標簽體的自定義標簽

1). 若一個標簽有標簽體

<mytag:testJspFragment>abcdefg</mytag:testJspFragment>

在自定義標簽的標簽處理器中使用 JspFragment 對象封裝標簽體信息。

2). 若配置了標簽含有標簽體

則 JSP 引擎會調用 setJspBody() 方法把 JspFragment 傳遞給標簽處理器類
在 SimpleTagSupport 中還定義了一個 getJspBody() 方法, 用於返回 JspFragment 對象。

3). JspFragment 的 invoke(Writer) 方法

把標簽體內容從 Writer 中輸出,若為 null,
則等同於 invoke(getJspContext().getOut()), 即直接把標簽體內容輸出到頁面上。


有時, 可以 借助於 StringWriter, 可以在標簽處理器類中先得到標簽體的內容:

//1. 利用 StringWriter 得到標簽體的內容.
StringWriter sw = new StringWriter();
bodyContent.invoke(sw);

//2. 把標簽體的內容都變為大寫
String content = sw.toString().toUpperCase();

4). 在 tld 文件中, 使用 body-content 節點來描述標簽體的類型

<body-content>: 指定標簽體的類型,大部分情況下, 取值為 scriptless。可能取值有 3 種:

  • empty: 沒有標簽體
  • scriptless: 標簽體可以包含 el 表達式和 JSP 動作元素,但不能包含 JSP 的腳本元素
  • tagdependent: 表示標簽體交由標簽本身去解析處理。若指定 tagdependent,在標簽體中的所有代碼都會原封不動的交給標簽處理器,而不是將執行結果傳遞給標簽處理器

<body-content>tagdependent</body-content>

5). 定義一個自定義標簽

<mytag:printUpper time="10">abcdefg</mytag> 把標簽體內容轉換為大寫, 並輸出 time 次到瀏覽器上。

6). 實現 forEach 標簽

> 兩個屬性: items(集合類型, Collection),var(String 類型)

> doTag:

* 遍歷 items 對應的集合
* 把正在遍歷的對象放入到 pageContext 中,鍵: var,值: 正在遍歷的對象.
* 把標簽體的內容直接輸出到頁面上。

<c:forEach items="${requestScope.customers }" var="cust2">
${pageScope.cust2.id } -- ${cust2.name } <br>
</c:forEach>

2. 開發有父標簽的標簽

    @Override
    public void doTag() throws JspException, IOException {
        //1. 得到父標簽的引用
        JspTag parent = getParent();
        
        //2. 獲取父標簽的 name 屬性
        ParentTag parentTag = (ParentTag) parent;
        String name = parentTag.getName();
        
        //3. 把 name 值打印到 JSP 頁面上.
        getJspContext().getOut().print("子標簽輸出name: " + name);
    }

1). 父標簽無法獲取子標簽的引用

父標簽僅把子標簽作為標簽體來使用

2). 子標簽可以通過 getParent() 方法來獲取父標簽的引用(需繼承 SimpleTagSupport 或自實現 SimpleTag 接口的該方法)
若子標簽的確有父標簽,JSP 引擎會把代表父標簽的引用通過 setParent(JspTag parent) 賦給標簽處理器

3). 父標簽的類型是 JspTag 類型

該接口是一個空接口,但是來統一 SimpleTag 和 Tag 的。實際使用需要進行類型的強制轉換。

4). 父標簽的 <body-content></body-content>需設置為 scriptless

在 tld 配置文件中,無需為父標簽有額外的配置。但,子標簽是是以標簽體的形式存在的,所以父標簽的 <body-content></body-content>需設置為 scriptless。

5). 實現

<c:choose>
<c:when test="${param.age > 24}">大學畢業</c:when>
<c:when test="${param.age > 20}">高中畢業</c:when>
<c:otherwise>高中以下...</c:otherwise>
</c:choose>

> 開發 3 個標簽: choose,when,otherwise
> 其中 when 標簽有一個 boolean 類型的屬性: test
> choose 是 when 和 otherwise 的父標簽
> when 在 otherwise 之前使用

> 在父標簽 choose 中定義一個 "全局" 的 boolean 類型的 flag:用於判斷子標簽在滿足條件的情況下是否執行。

* 若 when 的 test 為 true,且 when 的父標簽的 flag 也為 true,則執行 when 的標簽體(正常輸出標簽體的內容),
同時把 flag 設置為 false
* 若 when 的 test 為 true,且 when 的父標簽的 flag 為 false,則不執行標簽體。
* 若 flag 為 true,otherwise 執行標簽體。

如何開發自定義標簽