利用 SimpleTagSupport 開發自定義標籤



 

簡介: 
   絕大部分 Java 領域的 MVC 框架,例如 Struts、spring    MVC、JSF 等,主要由兩部分組成:控制器組件和視圖組件。其中視圖組件主要由大量功能豐富的標籤庫充當。對於大部分開發者而言,可能通常只使用這些框架提供的標籤,很少自己開發標籤;但如果開發者掌握針對特定應用開發自定義標籤,則可以大大簡化 Web 應用的表現層開發,而 JSP 2 規範提供的  impleTagSupport 爲開發自定義標籤提供了很好的支持。

自定義標籤庫並不是 JSP 2 纔出現的,JSP 1.1 版中已經增加了自定義標籤庫規範,自定義標籤庫是一種非常優秀的表現層組件技術。通過使用自定義標籤庫,可以在簡單的標籤中封裝複雜的功能。

爲什麼要使用自定義標籤呢?主要是爲了取代醜陋的 JSP 腳本。在 HTML 頁面中插入 JSP 腳本有如下幾個壞處:

  • JSP 腳本非常醜陋,難以閱讀。
  • JSP 腳本和 HTML 代碼混雜,維護成本高。
  • HTML 頁面中嵌入 JSP 腳本,導致美工人員難以參與開發。
             
  

出於以上三點的考慮,我們需要一種可在頁面中使用的標籤,這種標籤具有和 HTML 標籤類似的語法,但由可以完成 JSP 腳本的功能——這種標籤就是 JSP 自定義標籤。

在 JSP1.1 規範中開發自定義標籤庫比較複雜,JSP 2 規範簡化了標籤庫的開發,在 JSP 2 中開發標籤庫只需如下幾個步驟:

  1. 開發自定義標籤處理類;
  2. 建立一個 *.tld 文件,每個 *.tld 文件對應一個標籤庫,每個標籤庫對應多個標籤;
  3. 在 JSP 文件中使用自定義標籤。

開發自定義標籤類

標籤庫和實際開發

標籤庫是非常重要的技術,通常來說,初學者、普通開發人員自己開發標籤庫的機會很少,但如果希望成爲高級程序員,或者希望開發通用框架,就需要大量開發自定義標籤了。所有的 MVC 框架,如 Struts 2、SpringMVC、JSF 等都提供了豐富的自定義標籤。

當我們在 JSP 頁面使用一個簡單的標籤時,底層實際上由標籤處理類提供支持,從而可以使用簡單的標籤來封裝複雜的功能,從而使團隊更好地協作開發(能讓美工人員更好地參與 JSP 頁面的開發)。

早期 JSP 自定義標籤類開發過程略微複雜一些,但 JSP 2 已經簡化了這個過程,它只要自定義標籤類都必須繼承一個父類:javax.servlet.jsp.tagext.SimpleTagSupport,除此之外,JSP 自定義標籤類還有如下要求。

  • 如果標籤類包含屬性,每個屬性都有對應的 getter 和 setter 方法。
  • 重寫 doTag() 方法,這個方法負責生成頁面內容。

下面開發一個最簡單的自定義標籤,該標籤負責在頁面上輸出 HelloWorld。

// 標籤處理類,繼承 SimpleTagSupport 父類
public class HelloWorldTag extends SimpleTagSupport 
{ 
    // 重寫 doTag 方法,該方法在標籤結束生成頁面內容
    public void doTag()throws JspException, 
        IOException 
    { 
        // 獲取頁面輸出流,並輸出字符串
        getJspContext().getOut().write("Hello World"); 
    } 
} 
                  

上面這個標籤處理類非常簡單,它繼承了 SimpleTagSupport 父類,並重寫 doTag() 方法,而 doTag() 方法則負責輸出頁面內容。該標籤沒有屬性,因此無須提供 setter 和 getter 方法。

建立 TLD 文件

TLD 是 Tag Library Definition 的縮寫,即標籤庫定義,文件的後綴是 tld,每個 TLD 文件對應一個標籤庫,一個標籤庫中可包含多個標籤,TLD 文件也稱爲標籤庫定義文件。

標籤庫定義文件的根元素是 taglib,它可以包含多個 tag 子元素,每個 tag 子元素都定義一個標籤。通常我們可以到 Web 容器下複製一個標籤庫定義文件,並在此基礎上進行修改即可。例如 Tomcat6.0,在 webapps\examples\WEB-INF\jsp2 路徑下包含了一個 jsp2-example-taglib.tld 文件,這就是示範用的標籤庫定義文件。

將該文件複製到 Web 應用的 WEB-INF/ 路徑,或 WEB-INF 的任意子路徑下,並對該文件進行簡單修改,修改後的 mytaglib.tld 文件代碼如下:

<?xml version="1.0" encoding="GBK"?>
<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 web-jsptaglibrary_2_0.xsd"
    version="2.0">
    <tlib-version>1.0</tlib-version>
    <short-name>mytaglib</short-name>
    <!-- 定義該標籤庫的URI -->
    <uri>http://www.crazyit.org/mytaglib</uri>
    <!-- 定義第一個標籤 -->
    <tag>
        <!-- 定義標籤名 -->
        <name>helloWorld</name>
        <!-- 定義標籤處理類 -->
        <tag-class>lee.HelloWorldTag</tag-class>
        <!-- 定義標籤體爲空 -->
        <body-content>empty</body-content>
    </tag>
</taglib>
                  

上面標籤庫定義文件也是一個標準的 XML 文件,該 XML 文件的根元素是 taglib 元素,因此我們每次編寫標籤庫定義文件都直接添加該元素即可。

taglib 下有三個子元素:

  • tlib-version:指定該標籤庫實現的版本,這是一個作爲標識的內部版本號,對程序沒有太大的作用。
  • short-name:該標籤庫的默認短名,該名稱通常也沒有太大的用處。
  • uri:這個屬性非常重要,它指定該標籤庫的 URI,相當於指定該標籤庫的唯一標識。如上粗體字代碼所示,JSP 頁面中使用標籤庫時就是根據該 URI 屬性來定位標籤庫的。

除此之外,taglib 元素下可以包含多個 tag 元素,每個 tag 元素定義一個標籤,tag 元素下至少應包含如下三個子元素:

  • name:該標籤庫的名稱,這個屬性很重要,JSP 頁面中就是根據該名稱來使用此標籤的。
  • tag-class:指定標籤的處理類,毋庸置疑,這個屬性非常重要,指定了標籤由哪個 Java 類來處理。
  • body-content:這個屬性也很重要,它指定標籤體內容。該元素的值可以是如下幾個:
    • tagdependent:指定標籤處理類自己負責處理標籤體。
    • empty:指定該標籤只能作用空標籤使用。
    • scriptless:指定該標籤的標籤體可以是靜態 HTML 元素,表達式語言,但不允許出現 JSP 腳本。
    • JSP:指定該標籤的標籤體可以使用 JSP 腳本。

實際上由於 JSP 2 規範不再推薦使用 JSP 腳本,所以 JSP 2 自定義標籤的標籤體中不能包含 JSP 腳本。所以實際上 body-content 元素的值不可以是 JSP。

定義了上面的標籤庫定義文件後,將標籤庫文件放在 Web 應用的 WEB-INF 路徑,或任意子路徑下,Java Web 規範會自動加載該文件,則該文件定義的標籤庫也將生效

在 JSP 頁面中確定指定標籤需要 2 點:

  • 標籤庫 URI:確定使用哪個標籤庫。
  • 標籤名:確定使用哪個標籤。

使用標籤庫分成以下兩個步驟:

  1. 導入標籤庫:使用 taglib 編譯指令導入標籤庫,就是將標籤庫和指定前綴關聯起來。
  2. 使用標籤:在 JSP 頁面中使用自定義標籤。

taglib 的語法格式如下:

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

其中 uri 屬性確定標籤庫的 URI,這個 URI 可以確定一個標籤庫。而 prefix 屬性指定標籤庫前綴,即所有使用該前綴的標籤將由此標籤庫處理。

使用標籤的語法格式如下:

<tagPrefix:tagName tagAttribute=”tagValue” … > 
	<tagBody/> 
</tagPrefix:tagName> 
                  

如果該標籤沒有標籤體,則可以使用如下語法格式:

<tagPrefix:tagName tagAttribute=”tagValue” … /> 
                  

上面使用標籤的語法裏都包含了設置屬性值,前面我們介紹的 HelloWorldTag 標籤沒有任何屬性,所以使用該標籤只需用 <mytag:helloWorld/> 即可。其中 mytag 是 taglib 指令爲標籤庫指定的前綴,而 helloWorld 是標籤名。

下面是使用 helloWorld 標籤的 JSP 頁面代碼:

<%@ page contentType="text/html; charset=GBK"%>
<!-- 導入標籤庫,指定mytag前綴的標籤,
	由http://www.crazyit.org/mytaglib的標籤庫處理 -->
<%@ taglib uri="http://www.crazyit.org/mytaglib" prefix="mytag"%>
<html>
<head>
<title>自定義標籤示範</title>
</head>
<body bgcolor="#ffffc0">
<h2>下面顯示的是自定義標籤中的內容</h2>
<!-- 使用標籤 ,其中mytag是標籤前綴,根據taglib的編譯指令,
	mytag前綴將由http://www.crazyit.org/mytaglib的標籤庫處理 -->
<mytag:helloWorld/><BR>
</body>
</html>
                  

上面頁面中第一行粗體字代碼指定了 http://www.crazyit.org/mytaglib 標籤庫的前綴爲 mytag,第二行粗體字代碼表明使用 mytag 前綴對應標籤庫裏的 helloWorld 標籤。瀏覽該頁面將看到如圖 1 所示效果:


圖 1. 簡單標籤
圖 1. 簡單標籤

帶屬性的標籤

前面的簡單標籤既沒有屬性,也沒有標籤體,用法、功能都比較簡單。實際上還有如下兩種常用的標籤:

  • 帶屬性的標籤。
  • 帶標籤體的標籤。

正如前面介紹的,帶屬性標籤必須爲每個屬性提供對應的 setter 和 getter 方法。帶屬性標籤的配置方法與簡單標籤也略有差別,下面介紹一個帶屬性標籤的示例:

public class QueryTag extends SimpleTagSupport
{
    //標籤的屬性
    private String driver;
    private String url;
    private String user;
    private String pass;
    private String sql;
    //執行數據庫訪問的對象 
    private Connection conn = null;
    private Statement stmt = null;
    private ResultSet rs = null;
    private ResultSetMetaData rsmd = null;
    //標籤屬性driver的setter方法
    public void setDriver(String driver) {
        this.driver = driver; 
    }
        //標籤屬性url的setter方法
    public void setUrl(String url) {
        this.url = url; 
    }
        //標籤屬性user的setter方法
    public void setUser(String user) {
        this.user = user; 
    }
        //標籤屬性pass的setter方法
    public void setPass(String pass) {
        this.pass = pass; 
    }
        //標籤屬性driver的getter方法
    public String getDriver() {
        return (this.driver); 
    }
        //標籤屬性url的getter方法
    public String getUrl() {
        return (this.url); 
    }
        //標籤屬性user的getter方法
    public String getUser() {
        return (this.user); 
    }
        //標籤屬性pass的getter方法
    public String getPass() {
        return (this.pass); 
    }
        //標籤屬性sql的getter方法
    public String getSql() {
        return (this.sql); 
    }
        //標籤屬性sql的setter方法
    public void setSql(String sql) {
        this.sql = sql; 
    }
    public void doTag()throws JspException,
        IOException
    {
           try
        {
            //註冊驅動
            Class.forName(driver);
            //獲取數據庫連接
            conn = DriverManager.getConnection(url,user,pass);
            //創建Statement對象
            stmt = conn.createStatement();
            //執行查詢
            rs = stmt.executeQuery(sql);
            rsmd = rs.getMetaData();
            //獲取列數目
            int columnCount = rsmd.getColumnCount();
            //獲取頁面輸出流
            Writer out = getJspContext().getOut();
            //在頁面輸出表格
            out.write("<table border='1' bgColor='9999cc' width='400'>");
            //遍歷結果集
            while (rs.next())
            {
                out.write("<tr>");
                //逐列輸出查詢到的數據
                for (int i = 1 ; i <= columnCount ; i++ )
                {
                    out.write("<td>");
                    out.write(rs.getString(i));
                    out.write("</td>");
                }
                out.write("</tr>");
            }
        }
        catch(ClassNotFoundException cnfe)
        {
            cnfe.printStackTrace();
            throw new JspException("自定義標籤錯誤" + cnfe.getMessage());
        }
        catch (SQLException ex)
        {
            ex.printStackTrace();
            throw new JspException("自定義標籤錯誤" + ex.getMessage());
        }
        finally
        {
            //關閉結果集
            try
            {
                if (rs != null)
                    rs.close();
                if (stmt != null)
                    stmt.close();
                if (conn != null)
                    conn.close();
            }
            catch (SQLException sqle)
            {
                sqle.printStackTrace();
            }
        }
    }
}

上面這個標籤稍微複雜一點,它包含了 5 個屬性,如程序中粗體字代碼所示,則程序需要爲這 5 個屬性提供 setter 和 getter 方法。

該標籤輸出的內容依然由 doTag() 方法決定,該方法會根據 SQL 語句查詢數據庫,並將查詢結果顯示在當前頁面中。

對於有屬性的標籤,需要爲 tag 元素增加 attribute 子元素,每個 attribute 子元素定義一個屬性,attribue 子元素通常還需要指定如下幾個子元素:

  • name:設置屬性名,子元素的值是字符串內容。
  • required:設置該屬性是否爲不需屬性,該子元素的值是 true 或 false。
  • fragment:設置該屬性是否支持 JSP 腳本、表達式等動態內容,子元素的值是 true 或 false。

爲了配置上面的 QueryTag 標籤,我們需要在 mytaglib.tld 文件中增加如下配置片段:

<!-- 定義第二個標籤 -->
<tag>
    <!-- 定義標籤名 -->
    <name>query</name>
    <!-- 定義標籤處理類 -->
    <tag-class>lee.QueryTag</tag-class>
    <!-- 定義標籤體爲空 -->
    <body-content>empty</body-content>
    <!-- 配置標籤屬性:driver -->
    <attribute>
        <name>driver</name> 
        <required>true</required>
        <fragment>true</fragment>
    </attribute>
    <!-- 配置標籤屬性:url -->
    <attribute>
        <name>url</name> 
        <required>true</required>
        <fragment>true</fragment>
    </attribute>
    <!-- 配置標籤屬性:user -->
    <attribute>
        <name>user</name> 
        <required>true</required>
        <fragment>true</fragment>
    </attribute>
    <!-- 配置標籤屬性:pass -->
    <attribute>
        <name>pass</name> 
        <required>true</required>
        <fragment>true</fragment>
    </attribute>
    <!-- 配置標籤屬性:sql -->
    <attribute>
        <name>sql</name> 
        <required>true</required>
        <fragment>true</fragment>
    </attribute>
</tag>

上面 5 行粗體字代碼分別爲該標籤配置了 driver、url、user、pass 和 sql 等 5 個屬性,並指定這 5 個屬性都是必填屬性、而且屬性值支持動態內容。

配置完畢後,就可在頁面中使用標籤,先導入標籤庫,然後使用標籤。使用標籤的 JSP 頁面片段如下:

<!-- 導入標籤庫,指定mytag前綴的標籤,
    由http://www.crazyit.org/mytaglib的標籤庫處理 -->
<%@ taglib uri="http://www.crazyit.org/mytaglib" prefix="mytag"%>
...
<!-- 其他HTML內容 -->
<!-- 使用標籤 ,其中mytag是標籤前綴,根據taglib的編譯指令,
    mytag前綴將由http://www.crazyit.org/mytaglib的標籤庫處理 -->
<mytag:query
    driver="com.mysql.jdbc.Driver"
    url="jdbc:mysql://localhost:3306/javaee"
    user="root"
    pass="32147"
    sql="select * from newsinf"/>

在瀏覽器中瀏覽該頁面,效果如圖 2 所示。


圖 2. 使用帶屬性的標籤執行查詢
圖 2. 使用帶屬性的標籤執行查詢

圖 2 中看到從數據庫裏查詢到 2 條記錄,當然這也需要底層 javaee 數據庫裏包含 newsinf 數據表,且該數據表裏包含這兩條記錄才行。

在 JSP 頁面中只需要使用簡單的標籤,即可完成“複雜”的功能:執行數據庫查詢,並將查詢結果在頁面上以表格形式顯示。這也正是自定義標籤庫的目的——以簡單的標籤,隱藏複雜的邏輯。

當然,並不推薦在標籤處理類中訪問數據庫,因爲標籤庫是表現層組件,它不應該包含任何業務邏輯實現代碼,更不應該執行數據庫訪問,它只應該負責顯示邏輯

帶標籤體的標籤,可以在標籤內嵌入其他內容(包括靜態的 HTML 內容和動態的 JSP 內容),通常用於完成一些邏輯運算,例如判斷和循環等。下面以一個迭代器標籤爲示例,介紹帶標籤體標籤的開發過程。

一樣先定義一個標籤處理類,該標籤處理類的代碼如下:

public class IteratorTag extends SimpleTagSupport
{
    //標籤屬性,用於指定需要被迭代的集合
    private String collection;
    //標籤屬性,指定迭代集合元素,爲集合元素指定的名稱
    private String item;
    //collection屬性的setter和getter方法
    public void setCollection(String collection)
    {
        this.collection = collection;
    }
    public String getCollection()
    {
        return this.collection;
    }
    //item屬性的setter和getter方法
    public void setItem(String item)
    {
        this.item = item;
    }
    public String getItem()
    {
        return this.item;
    }
    //標籤的處理方法,簡單標籤處理類只需要重寫doTag方法
    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);
        }
    }
}

上面標籤處理類與前面處理類並沒有太大的不同,該處理類包含 2 個屬性,併爲這兩個屬性提供了 setter 和 getter 方法。標籤處理類的 doTag 方法首先從 page 範圍內獲取了指定名稱的 Collection 對象,然後遍歷 Collection 對象的元素,每次遍歷都調用了 getJspBody() 方法,如程序中粗體字代碼所示,該方法返回該標籤所包含的標籤體:JspFragment 對象,執行該對象的 invoke() 方法,即可輸出標籤體內容。該標籤的作用是:遍歷指定集合,每遍歷一個集合元素,即輸出標籤體一次。

因爲該標籤的標籤體不爲空,配置該標籤時指定 body-content 爲 scriptless,該標籤的配置代碼片段如下代碼所示:

<!-- 定義第三個標籤 -->
<tag>
    <!-- 定義標籤名 -->
    <name>iterator</name>
    <!-- 定義標籤處理類 -->
    <tag-class>lee.IteratorTag</tag-class>
    <!-- 定義標籤體支持JSP腳本 -->
    <body-content>scriptless</body-content>
    <!-- 配置標籤屬性:collection -->
    <attribute>
        <name>collection</name> 
        <required>true</required>
        <fragment>true</fragment>
    </attribute>
    <!-- 配置標籤屬性:item -->
    <attribute>
        <name>item</name> 
        <required>true</required>
        <fragment>true</fragment>
    </attribute>
</tag>

上面配置片段中粗體字代碼指定該標籤的標籤體可以是靜態 HTML 內容,也可以是表達式語言。

爲了測試在 JSP 頁面中使用該標籤的效果,我們首先把一個 List 對象設置成 page 範圍的屬性,然後使用該標籤來迭代輸出 List 集合的全部元素。

JSP 頁面代碼如下:

<%@ page import="java.util.*"%>
<%@ page contentType="text/html; charset=GBK"%>
<!-- 導入標籤庫,指定mytag前綴的標籤,
    由http://www.crazyit.org/mytaglib的標籤庫處理 -->
<%@ taglib uri="http://www.crazyit.org/mytaglib" prefix="mytag"%>
<html>
    <head>
        <title>帶標籤體的標籤-迭代器標籤</title>
    </head>
    <body>
        <h2>帶標籤體的標籤-迭代器標籤</h2>
        <hr>
        <%
        //創建一個List對象
        List<String> a = new ArrayList<String>();
        a.add("hello");
        a.add("world");
        a.add("java");
        //將List對象放入page範圍內
        pageContext.setAttribute("a" , a);
        %>
        <table border="1" bgcolor="aaaadd" width="300">
        <!-- 使用迭代器標籤,對a集合進行迭代 -->
        <mytag:iterator collection="a" item="item">
            <tr>
                <td></td>
            <tr>
        </mytag:iterator>
        </table>
    </body>
</html>

上面頁面代碼中粗體字代碼即可實現通過 iterator 標籤來遍歷指定集合,瀏覽該頁面即看到如圖 3 所示界面:


圖 3. 帶標籤體的標籤
圖 3. 帶標籤體的標籤

圖 3 顯示了使用 iterator 標籤遍歷集合元素的效果,從 iteratorTag.jsp 頁面的代碼來看,使用 iterator 標籤遍歷集合元素比使用 JSP 腳本遍歷集合元素要優雅得多,這就是自定義標籤的魅力。

實際上 JSTL 標籤庫提供了一套功能非常強大標籤,例如普通的輸出標籤,像我們剛剛介紹的迭代器標籤,還有用於分支判斷的標籤等等,JSTL(JSP 標準標籤庫)都有非常完善的實現。除此之外,Apache 下還有一套 DisplayTags 的標籤庫實現,做得也非常不錯

關於作者

李剛,從事 Java EE 應用開發近 10 年。曾任 LITEON 公司的 J2EE 技術主管,負責該公司的企業信息化平臺的架構設計。曾任廣州電信、廣東龍泉科技等公司的技術培訓教師。瘋狂 Java 聯盟(http://www.crazyit.org)站長。瘋狂 Java 實訓營創始人,瘋狂 Java 體系圖書作者,曾任東方標準廣州中心軟件教學總監,曾兼任廣東技術師範學院計算機科學系的兼職副教授。國內知名IT技術作家,已出版《瘋狂 Java 講義》、《輕量級 Java EE 企業應用實戰》、《瘋狂 Ajax 講義》、《Struts 2.1 權威指南》、《Ruby On Rails 敏捷開發最佳實踐》等著作。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章