天天看點

JSP 2開發自定義标簽JSP2開發自定義标簽

JSP2開發自定義标簽

目錄

  • JSP2開發自定義标簽
    • 目錄
    • 開發自定義标簽的步驟
    • 開發簡單的自定義标簽
      • 自定義标簽處理類
      • 标簽處理類的聲明周期
      • 建立TLD檔案
      • 注冊标簽
      • 使用标簽庫
    • 帶屬性的标簽
    • 帶有标簽體的标簽
    • 以頁面片段作為屬性标簽
    • 動态屬性标簽
    • 源碼

1. 開發自定義标簽的步驟

JSP2的規範簡化了标簽庫的開發,在原本的JSP1.1中開發自定義标簽比較複雜。

JSP2中開發自定義标簽隻需要如下的幾步:

  1. 開發自定義标簽處理類。
  2. 建立一個*.tld檔案,每個檔案對應一個标簽庫,每個标簽庫可以包含多個标簽。
  3. 在JSP檔案中使用自定義标簽(測試)。

2. 開發簡單的自定義标簽

1. 自定義标簽處理類

自定義标簽類繼承一個父類:javax.servlet.tagext.SimpleTagSupport,除此之外,JSP自定義标簽還有如下的要求:

  • 對于所有的标簽類的屬性,都要有對應的gettter和setter方法。
  • 重寫doTag()方法,這個方法負責生成頁面内容。

下面是一個簡單的輸出HelloWorld的标簽。

public class HelloWorldTag extends SimpleTagSupport {

    // 重寫doTag()方法,該方法文便簽生成标簽頁
    @Override
    public void doTag() throws JspException, IOException {
        // 擷取頁面輸出流,并輸出字元
        getJspContext().getOut().write("Hello World "
                + new java.util.Date());
    }
}
           

上面的标簽處理類繼承了SimpleTagSupport父類,并重寫doTag()方法,而doTag()輸出頁面的内容。該标簽頁面沒有提供屬性,是以無需提供getter和setter方法。

2. 标簽處理類的聲明周期

  1. JSP容器通過調用其無參構造,建立一個簡單的标簽處理類執行個體。是以,簡單的标簽處理器必須有一個無參構造器。
  2. JSP容器調用setJspContext方法,同時傳遞一個JSPContext對象。JspContext最重要的方法時getOut,它傳回一個JspWriter,用于将相應發送到用戶端。setJspContext方法的簽名如下:

    public void setJspContext(JspContext jspContext)

    大多數時候,會需要将傳進的JspContext付一個類變量,以便後續使用。
  3. 如果表示标簽處理類的定制标簽是嵌套在另一個标簽中的,JSP容器就會調用setParent方法。
  4. JSP容器為改标簽的定義的每個屬性都調用設定方法(setter)
  5. 如果标簽中有主體内容,JSP将調用SimpleTag接口的setJspBody方法,将主體内容作為JspFragment。如果沒有主體内容,則JSP容器不會調用這個方法。
  6. JSP容器調用doTag方法,所有變量在doTag方法傳回是進行同步。

3. 建立TLD檔案。

首先,何為TLD?TLD是Tag Library Definitation的縮寫,即标簽庫檔案的定義,檔案的字尾是tld,每個TLD檔案對應一個标簽庫,一個标簽庫中可以包含多個标簽。TLD檔案也被稱為标簽庫定義檔案。每個标簽都需要在該檔案中注冊,類似于Servlet在web.xml中注冊。

标簽庫檔案的根元素是taglib,他可以包含多個tag子元素,每一個子元素定義一個标簽。Tomcat的Webapps\examples\WEB-INF\jsp2路徑下有一個jsp2-example-taglib.tld檔案,該檔案是一個TLD檔案的示例。

可以将該檔案複制到WEB-INF/路徑下,或者其它的子路徑下,複制并修改後的内容如下:

<?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">
        <tlib-version>1.0</tlib-version>
        <short-name>mytaglib</short-name>
        <uri></uri>
</taglib>
           

标簽庫檔案也是一個标準的XML檔案,該檔案的根元素是taglib元素。

可以在這個檔案中注冊自定義的标簽。

從上面的代碼中可以看出taglib有如下的3個子元素。

  • tlib-version:指的是該庫的版本,這是一個座位辨別的内部版本号。
  • short-name:該标簽的預設的短名,該名稱通常沒有太大的作用。
  • uri:這個屬性非常的重要,他指定了該标簽庫的URI,相當于标簽庫的唯一辨別。JSP頁面中使用标簽庫時就是根據該URI來定位标簽庫的。URI有兩種路徑:
    • 相對路徑:可以在該檔案不使用URI,而在JSP檔案中直接定位到該檔案。(使用自定義标簽時會說到)
    • 絕對路徑:使用網址+路徑作為統一資源定位,這個在釋出自定義标簽時非常重要。

是以,在練習是使用相對路徑。當然如果有域名也可以使用絕對路徑,如果不想使用相對路徑,有沒有域名,可以使用http://www.example.com,這是一個可以做測試的一個域名。

打開之後:

JSP 2開發自定義标簽JSP2開發自定義标簽

翻譯過來大概是:該域被建立用于文檔中的說明性示例。您可以在沒有事先協調或要求許可的情況下使用此域。

4. 注冊标簽

在taglib還可以包含多個tag元素,每個tag元素可以定義一個标簽,tag元素下允許出現如下常用元素:

  • name:該标簽的名稱,在JSP頁面中就是根據該名稱來使用此标簽的。
  • tag-class:指定标簽的處理類,他指定了标簽有那個标簽處理類來處理。
  • body-content:指定标簽體的内容。該元素的值可以是如下幾個。
    • tagdependent:指定标簽處理類自己負責處理标簽體。
    • empty:指定該标簽隻能作為空标簽使用。
    • scriptless:指定該标簽的标簽體可以是靜态的HTML元素、表達式語言(EL),但不允許出現JSP腳本。
  • dynamic-attributes:指定該标簽是否支援動态屬性。隻有當定義動态屬性标簽時才是用該元素。

    下面的程式注冊了hello标簽,所使用的類是前面出現的類:

<?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">
        <tlib-version>1.0</tlib-version>
        <short-name>mytaglib</short-name>
        <uri>http://www.example.com/mytaglib</uri>
    <tag>
        <!-- 标簽的名字 -->
        <name>hello</name>
        <!-- 标簽的處理類 -->
        <tag-class>com.example.mytagClass.HelloWorldTag</tag-class>
        <!-- 定義标簽體 -->
        <body-content>empty</body-content>
    </tag>
</taglib>
           

5. 使用标簽庫

在JSP頁面中使用标簽庫需要注意如下兩點:

  1. 标簽庫的URI:确定使用哪個标簽庫。
  2. 标簽名:确定使用那個标簽。

使用标簽庫導入标簽庫,使用taglib編譯指令導入标簽庫,就是将标簽庫和指定的字首關聯起來。

taglib編譯指令的文法如下:

<%@ taglib prefix="tagPrefix" uri="tagliburi" %>

其中uri屬性指定标簽庫的URI,這個URI可以确定一個标簽庫。而prefix屬性指定标簽庫的字首,即所有使用該字首的标簽将由該标簽庫處理。

使用标簽的文法如下:

<tagPrefix:tagName tagAttribute="tagValue"...>
    <tagBody/>
</tagPrefix:tagName>
           

如果該标簽沒有标簽體,則可以使用如下的文法:

<tagPrefix:tagName tagAttribute="tagValue".../>
           

上面使用的标簽的文法都使用了标簽體,而hello标簽沒有任何屬性,是以該标簽隻需要使用

<mytag:hello/>

即可。

如下:

<%@ page contentType="text/html;charset=UTF-8"
         language="java" %>
<%@taglib prefix="hello" uri="http://www.example.com/taglib" %>
<%-- 如果前面沒有指定URI,則可以這樣使用 --%>
<%-- <%@taglib prefix="hello" uri="/WEB-INF/mytaglib.tld" %> --%>
<html>
<head>
    <title>Hello Tag</title>
</head>
<body>
Hello!!!<br/>
<hello:helloWorld></hello:helloWorld>
</body>
</html>
           

運作之後可以看到如下效果:

JSP 2開發自定義标簽JSP2開發自定義标簽

3. 帶屬性的标簽

前面說的标簽即沒有屬性,也沒有标簽題,用法、功能都比較簡單。

下面的就是一個帶有屬性的标簽:

import javax.servlet.jsp.JspContext;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspWriter;
import javax.servlet.jsp.tagext.SimpleTagSupport;
import java.io.IOException;
import java.util.StringTokenizer;

public class DataFormatterTag extends SimpleTagSupport {

    private String header;
    private String item;

    public String getHader() {
        return header;
    }

    public void setHeader(String header) {
        this.header = header;
    }

    public String getItem() {
        return item;
    }

    public void setItem(String item) {
        this.item = item;
    }

    @Override
    public void doTag() throws JspException, IOException {
        JspContext jspContext = getJspContext();
        JspWriter out = jspContext.getOut();
        out.println("<table style='border:1px solid green'>\n"
             + "<tr><td><span style='font-weight:blod'>"
            + header + "</span></td></tr>");
        StringTokenizer tokenizer = new StringTokenizer(item, ",");
        while (tokenizer.hasMoreTokens()) {
            String token = tokenizer.nextToken();
            out.print("<tr><td>" + token + "</td><tr>\n");
        }
        out.print("</table>");
    }

}
           

前面說過每個屬性都必須設定setter和getter,是以上面的header和item都有getter和setter方法,要開發的這個标簽的作用是,擷取一個表頭和item生成一個表格。其中item是一個用“,”隔開的字元串,然後使用StringTokenizer解析成items屬性。

接下來就是注冊該标簽了.

對有屬性的标簽,需要在<tag…/>元素增加<attribute…/>子元素,每個attribute子元素定義了一個标簽屬性。<attribute…/>子元素通常還需用定義如下的幾個元素。

  • name:設定屬性名,則元素的值是字元串的内容。
  • reqired:設定該屬性是否必須屬性,該子元素的值true或false。
  • fragment:設定該屬性是否支援JSP腳本、表達式等動态語言、子元素的值是true或false。

下面的是為上面的DataFormatterTag注冊,在mytablib.tld檔案中增加如下配置片段。

<tag>
    <name>dataFormatter</name>
    <tag-class>com.example.myTag.DataFormatterTag</tag-class>
    <body-content>empty</body-content>
    <attribute>
        <name>header</name>
        <required>true</required>
        <fragment>false</fragment>
    </attribute>
    <attribute>
        <name>items</name>
        <required>true</required>
        <fragment>false</fragment>
    </attribute>
</tag>
           

上面的代碼分别配置了header和items屬性。

下面就是使用該标簽了。

<%@ page contentType="text/html;charset=UTF-8"
         language="java" %>
<%@taglib prefix="simple" uri="/WEB-INF/mytaglib.tld" %>
<html>
<head>
    <title>測試DataFormatterTag</title>
</head>
<body>
<simple:dataFormatter header="字母" items="A,B,C,D"/>
<br/>
<simple:dataFormatter header="國家">
    <jsp:attribute name="items">
        美國,中國,加拿大
    </jsp:attribute>
</simple:dataFormatter>
</body>
</html>
           

上面的代碼使用了兩次的dataFormatter标簽,并且以兩種不同的方式傳遞屬性,一種是标簽屬性,一種使用的attribute标準動作指令。

下面的是他的效果:

JSP 2開發自定義标簽JSP2開發自定義标簽

4. 帶有标簽體的标簽

帶有标簽體的标簽可以切入其他内容。通常用于完成一些邏輯運算,例如循環和判斷等。下面以一個疊代器标簽為示例,介紹下。

首先定義一個标簽處理類:

import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.SimpleTagSupport;
import java.io.IOException;
import java.util.Collection;

public class IteratorTag extends SimpleTagSupport {
    // 标簽屬性,用于指定需要被疊代的集合
    private String collection;
    // 标簽屬性,指定被疊代的集合元素,為集合元素指定名稱
    private String item;

    public String getCollection() {
        return collection;
    }

    public void setCollection(String collection) {
        this.collection = collection;
    }

    public String getItem() {
        return item;
    }

    public void setItem(String item) {
        this.item = item;
    }

    // 處理标簽的方法,在标簽處理類中隻需要重寫doTag()方法

    @Override
    public void doTag() throws JspException, IOException {
        // 從page scope中擷取名為collection的集合
        Collection itemList = (Collection)getJspContext().getAttribute(collection);
        // 周遊集合
        for (Object s : itemList) {
            // 将集合元素設定到page範圍内
            getJspContext().setAttribute(item, s);
            // 輸出标簽體
            getJspBody().invoke(null);
        }
    }
}

           

上面的标簽處理類與之前的類基本一樣,隻是在周遊collection的時就調用getJspBody()方法,該方法傳回标簽所包含的标簽體:JspFragment對象,執行invoke()方法,即可輸出标簽體的内容。該标簽的作用是:每周遊一個集合元素,即輸出标簽體一次。

因為标簽體不為空,是以配置是指定body-content為sctiptless,注冊該标簽的代碼如下:

<tag>
        <!-- 定義标簽名 -->
        <name>iterator</name>
        <!-- 定義标簽處理類 -->
        <tag-class>com.example.myTag.IteratorTag</tag-class>
        <!-- 定義标簽體不允許出現JSP腳本 -->
        <body-content>scriptless</body-content>
        <!-- 配置标簽屬性:collection -->
        <attribute>
            <name>collection</name>
            <required>true</required>
            <fragment>true</fragment>
        </attribute>
        <!-- 配置标簽屬性 -->
        <attribute>
            <name>item</name>
            <required>true</required>
            <fragment>true</fragment>
        </attribute>
    </tag>
    <tag>
           

上面的代碼中指定該标簽體可以是靜态的HTML内容,也可以是表達式語言,但不允許是JSP腳本。

為了測試在JSP頁面中的使用效果,下面将一個List對象設定成page範圍的屬性,然後該标簽來疊代輸出LIst集合的全部元素。

<%@ page contentType="text/html;charset=UTF-8"
         language="java" %>
<%@taglib prefix="mytag" uri="/WEB-INF/mytaglib.tld"%>
<html>
<head>
    <title>測試帶标簽體的标簽</title>
</head>
<body>
<h2>帶有标簽體的标簽-疊代器标簽</h2>
<%
    // 建立一個List對象
    List<String> a = new ArrayList<String>();
    a.add("中國");
    a.add("美國");
    a.add("英國");
    // 将List對象放進page範圍
    pageContext.setAttribute("a", a);
%>
<table border="1" bgcolor="#7fffd4" width="300">
    <!-- 使用疊代器标簽,對a集合進行疊代 -->
    <mytag:iterator item="item" collection="a">
        <tr>
            <td>${pageScope.item}</td>
        </tr>
    </mytag:iterator>
</table>
</body>
</html>
           

下面是該便簽的測試效果圖:

JSP 2開發自定義标簽JSP2開發自定義标簽

5. 以頁面片段作為屬性标簽

JSP2規範的自定義标簽還允許直接将一段“頁面片段”作為屬性,這種方式給自定義标簽提供了更大的靈活性。

以“頁面片段”為屬性的标簽與普通的标簽差別并不大,在該标簽處理類定義一個JSPFragment類型的屬性,即表明該标簽允許使用“頁面片段”類型的屬性。

import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspWriter;
import javax.servlet.jsp.tagext.JspFragment;
import javax.servlet.jsp.tagext.SimpleTagSupport;
import java.io.IOException;

public class FragmentTag extends SimpleTagSupport {
    private JspFragment fragment;

    public JspFragment getFragment() {
        return fragment;
    }

    public void setFragment(JspFragment fragment) {
        this.fragment = fragment;
    }

    @Override
    public void doTag() throws JspException, IOException {
        JspWriter out = getJspContext().getOut();
        out.println("<div style='padding:10px;border:1px solid black;"
                + ";border-radius:20px'>");
        out.println("<h3>下面是動态傳入的JSP片段</h3>");
        // 調用、輸出“頁面片段”
        fragment.invoke(null);
        out.println("</div>");
    }
}
           

上面程式中定義的fragment成員變量,該成員變量代表了使用該标簽時的“頁面片段”。是以注冊是與普通并無任何差別。

<tag>
        <!-- 定義标簽名 -->
        <name>fragment</name>
        <!-- 定義标簽處理類 -->
        <tag-class>com.example.myTag.FragmentTag</tag-class>
        <!-- 指定标簽不支援标簽體 -->
        <body-content>empty</body-content>
        <!-- 定義标簽屬性:fragment -->
        <attribute>
            <name>fragment</name>
            <required>true</required>
            <fragment>true</fragment>
        </attribute>
    </tag>
           

由于該标簽需要一個fragment屬性,該屬性的類型是JspFragment,是以使用該标簽時需要使用<jsp:attribute…/>動作指令來設定屬性值,如下所示:

<%@ page contentType="text/html;charset=UTF-8"
         language="java" %>
<%@taglib prefix="mytag" uri="/WEB-INF/mytaglib.tld" %>
<html>
<head>
    <title>測試:以頁面片段作為标簽屬性</title>

</head>
<body>
<h2>下面顯示的是自定義标簽中的内容</h2>
<mytag:fragment>
    <jsp:attribute name="fragment">
    <%-- 使用後jsp:attribute标簽傳入fragment參數(改注釋不能放在fragment内) --%>
        <mytag:helloWorld/>
    </jsp:attribute>
</mytag:fragment>
<br/>
<mytag:fragment>
    <jsp:attribute name="fragment">
        <!-- 下面是動态的JSP頁面 -->
        ${pageContext.request.remoteAddr}
    </jsp:attribute>
</mytag:fragment>
</body>
</html>
           

由于程式制定fragment标簽的标簽體為empty,是以程式中國fragment開始和Fragment結束标簽之間隻能使用<jsp:attribute…/>子元素,甚至注釋都不行。

效果如下圖所示:

JSP 2開發自定義标簽JSP2開發自定義标簽

6. 動态屬性标簽

有時候标簽的屬性個數是不确定的,屬性名也不确定,這時候就需要借助動态屬性标簽了。

動态屬性标簽有兩個額外的要求:

  • 标簽的處理類還需要實作DynAttribute接口
  • 配置便簽時通過<dynamic-attributes…/>指定該标簽所支援的動态屬性。

下面是一個動态屬性标簽處理類。

import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspWriter;
import javax.servlet.jsp.tagext.DynamicAttributes;
import javax.servlet.jsp.tagext.SimpleTagSupport;
import java.io.IOException;
import java.util.ArrayList;

public class DynaAttributesTag extends SimpleTagSupport
        implements DynamicAttributes {
    // 儲存每個屬性名的集合
    private ArrayList<String> keys = new ArrayList<>();
    // 儲存每個屬性值的集合
    private ArrayList<Object> values = new ArrayList<>();

    @Override
    public void doTag() throws JspException, IOException {
        JspWriter out = getJspContext().getOut();
        // 此處隻是簡單的輸出每個屬性值
        out.println("<ol>");
        for (int i = ; i < keys.size(); i++) {
            String key = keys.get(i);
            Object value = values.get(i);
            out.println("<li>" + key + " = " + value + "</li>");
        }
        out.println("</ol>");
    }

    @Override
    public void setDynamicAttribute(String uri, String localName, Object value) throws JspException {
        // 添加屬性名
        keys.add(localName);
        // 添加屬性值
        values.add(value);
    }
}
           

上面的标簽屬性處理類實作了DynamicAttribute接口,該方法用于為該标簽處理類動态的添加屬性名和屬性值。标簽的屬性名和屬性值分别私用ArrayList<String>類型的keys和ArrayList<Object>類型的values儲存。

注冊該标簽時需要額外的指定<dynamic-attributes…/>子元素,來表明該标簽是動态屬性的标簽。下面是該屬性的注冊代碼片段:

<tag>
        <name>dynaAttr</name>
        <tag-class>com.example.myTag.DynaAttributesTag</tag-class>
        <body-content>empty</body-content>
        <!-- 指定支援動态屬性 -->
        <dynamic-attributes>true</dynamic-attributes>
    </tag>
           

一旦定義了動态屬性的标簽,接下來在頁面中使用該屬性時将十分的靈活,完全可以在為該标簽設定任意的屬性。如下:

<%@ page contentType="text/html;charset=UTF-8"
         language="java" %>
<%@taglib prefix="mytag" uri="/WEb-INF/mytaglib.tld" %>
<html>
<head>
    <title>測試動态屬性的标簽</title>
</head>
<body>
<h2>下面顯示的是自定義标簽的内容</h2>
<h4>指定兩個屬性</h4>
<mytag:dynaAttr name="你的名字" age="10000"/>
<h4>指定4個屬性</h4>
<mytag:dynaAttr name="星武者" age="20" work="暫無" birthday="****年**月**日"/>
</body>
</html>
           

下面是該标簽的效果:

JSP 2開發自定義标簽JSP2開發自定義标簽

7. 源碼

想要獲得源碼點選下方按鈕:

按鈕