JSP2自定義標籤庫技術

1、概述:
    JSP本質其實仍然是Servlet,部署JSP後,服務器會將其自動轉化成Servlet進行運行。用JSP取代Servlet的原因是利用Servlet實現用戶界面比較繁瑣,將大量的靜態HTML代碼穿插於Java代碼中增加了代碼維護工作的難度,並且也不利於前端開發人員和美工人員參與項目開發。
    JSP 2 標準爲開發者提供了自定義標籤庫的功能。使用自定義標籤庫的目的跟使用JSP的目的類似,爲了使系統的界面開發和邏輯處理解耦,雖然使用JSP頁面可以將界面開發和負責處業務邏輯的Servlet相分離,但是JSP頁面中依然存在由Java代碼構成的JSP腳本。引入自定義標籤庫技術使得開發者可以用自定義的標籤來取代JSP頁面中的Java腳本。使得前端的開發更加簡潔,也更容易維護。
    下文現總結了自定義標籤庫開發步驟。

2、基本用法:
    JSP 2 標準下定義自定義標籤庫有以下幾個步驟:
① 開發自定義標籤處理類。
② 編寫標籤庫配置文件,格式爲*.tld,一個配置文件配置了一個標籤庫,而一個標籤庫中可以擁有多個標籤。
③ 在JSP頁面中使用該標籤。
   
具體步驟如下:
·開發自定義標籤處理類:
    當JSP頁面遇到自定義標籤時,服務器會根據配置文件查找標籤處理類來處理標籤,每一個標籤處理類定義了一個標籤的處理方法。
    自定義標籤處理類要求繼承一個父類:javax.servlet.jsp.tagext.SimpleTagSupport。同時還有如下2個要求:
    ① 如果標籤類中包含屬性,每個屬性必須要有GetterSetter方法。 
    ② 重寫doTag方法,這個方法定義了標籤的處理過程。
    樣例如下:
package tag;

import java.io.IOException;

import javax.servlet.jsp.tagext.SimpleTagSupport;

public class Tag1 extends SimpleTagSupport{
    public void doTag() throws IOException {
        getJspContext().getOut().write("<h1>tag test</h1>");
    }
}

    爲了簡潔,我們這裏doTag方法中僅僅有一行代碼,向頁面輸出“tag test”的字樣,通過getJspContext().getOut()可以得到JSP頁面的輸出流,然後使用write方法輸入文本文件。這樣,就定義了一個標籤處理類。

·編寫標籤庫配置文件 *.tld:
    配置文件本質上是一個標準的XML文件。該文件給出了一個標籤庫的配置信息,其中包含了多個標籤的配置信息。這裏給出樣例如下:
<?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>abc</uri>
	<tag>
		<name>tag1</name>
		<tag-class>tag.Tag1</tag-class>
		<body-content>empty</body-content>
	</tag>
</taglib>


mytaglib.tld文件定義了一個新的標籤庫,其中配置了一個標籤tag1,文件中各個參數的意義如下:
        (1) 根元素是<taglib>,代表一個標籤庫,其中主要有3個子元素:
① <tlib-version> 該元素指明瞭標籤庫實現的版本,表示標籤庫內部的版本號,這個標籤對程序沒有太大的作用
 <short-name> 該元素指定了標籤庫的默認短命,該名稱也沒有太大的用處。
③ <uri> 這個子元素非常重要,它指明瞭該標籤庫的uri,是標籤庫的唯一標識,JSP頁面中會使用uri來定位標籤庫。這裏我們定義爲:abc
        (2) 標籤庫中的每個標籤用一個<tag>子元素來配置一個標籤,它包含以下子元素:
① <name> 該元素指明瞭標籤的名稱。
② <tag-class> 該元素指明瞭標籤的處理類,這裏我們配置了上文中實現的類Tag1
③ <body-content> 該元素指定了標籤體的具體內容,這裏我們設定爲empty,表示該標籤沒有標籤體,只能以空標籤的形式使用,該元素還可以指定以下的值:
tagdependent:指定標籤處理類自己負責處理標籤體。
scriptless:指定標籤體可以是靜態的HTML元素、表達式語言(EL),但不允許出現JSP腳本。
dynamic-attributes:指定標籤是否支持動態屬性。
    tld文件需要放置在web項目中的WEB-INF目錄下(雖然筆者的教材中說明需要放置在classes目錄下,但筆者親測發現,放在WEB-INF目錄下才有效)。

·在JSP頁面中使用標籤:
    要在JSP頁面中使用自定義標籤,首先需要用編譯指令<%@tablib...%> 導入標籤庫,格式如下:
        <%taglib uri="..." prefix="..." %>
其中uri就是tld文件中配置的uri,prefix則表示標籤的前綴,可以隨意設定,對於本例,則寫法如下:
        <%taglib uri="abc" prefix="mytag" %>
    導入標籤庫後,就可以在頁面中使用自定義標籤了,格式如下:
        <prefix:name .../>
其中:prefix就是編譯指令中指定的前綴名,name就是標籤的名稱,也就是tld文件中指定的<tag>元素中的<name>子元素的值,對於本例,寫法如下:
        <mytag:tag />
以下爲jsp頁面的樣例:
<%@ page language="java" contentType="text/html; charset=GBK"
	pageEncoding="GBK"%>
<%@taglib uri="abc" 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=ISO-8859-1">
<title>Insert title here</title>
</head>
	<body>
		<mytag:tag1 />
		<mytag:tag1 />
	</body>
</html>


我們在代碼中使用了2次自定義的標籤。
    以上就是自定義標籤庫的基本配置方式,這裏的示例項目的目錄結構如下圖:
 
    運行項目,可以看到如下的結果:
 
可以看到,自定義標籤產生了預想的效果。
    需要說明的是,在標籤處理過程中,向JSP輸出流中也可以輸出HTML標籤,此時,頁面也會顯示對應的效果,例如將代碼1中的第9行改爲:
     getJspContext().getOut().write("<h1>tag test</h1>");
    顯示效果如下: 
     
 

3、帶屬性的標籤:
    
 上文示例中定義的標籤沒有屬性,要想定義帶屬性的標籤,要在標籤處理類和標籤庫配置文件中對標籤的屬性進行配置,標籤中的屬性和標籤處理類中的成員變量是一一對應的,並且必須包含對應的GetterSetter方法。

·靜態屬性標籤:
    靜態屬性的標籤包含的屬性個數、屬性名稱都是固定的,由配置文件決定標籤包含哪些屬性。下面定義一個包含2個屬性的標籤tag2,格式如下:
                    <mytag:tag2 attr1="value1" attr2="value2" />
其中:attr1attr2爲該標籤的屬性。 
    首先定義標籤處理類,代碼如下:
package tag;

import java.io.IOException;

import javax.servlet.jsp.tagext.SimpleTagSupport;

public class Tag2 extends SimpleTagSupport{
    String attr1;
    String attr2;
    
    public String getAttr1() {
        return attr1;
    }

    public void setAttr1(String attr1) {
        this.attr1 = attr1;
    }

    public String getAttr2() {
        return attr2;
    }

    public void setAttr2(String attr2) {
        this.attr2 = attr2;
    }

    public void doTag() throws IOException {
        getJspContext().getOut().write("attr1 = " + attr1 + "<br>"
                + "attr2 = " + attr2);
    }
}

Tag2類中包含兩個數據域,attr1attr2,對應標籤中的2個屬性,注意數據域的類型均爲字符串類型。在doTag方法中,我們向頁面輸出屬性名稱和屬性值。
    然後在tld文件中對tag2標籤進行配置,添加新的<tag>子元素,如下:
<tag>
		<name>tag2</name>
		<tag-class>tag.Tag2</tag-class>
		<body-content>empty</body-content>
		<attribute>
			<name>attr1</name>
			<required>true</required>
			<fragment>false</fragment>
		</attribute>
		<attribute>
			<name>attr2</name>
			<required>true</required>
			<fragment>true</fragment>
		</attribute>
</tag>
    tag2的配置和tag1基本相同(這裏筆者將上一個自定義標籤的名稱改爲了tag1),不同的是需要用<attribute>子元素配置標籤的屬性,<attribute>又包含三個子元素:
    name:指定標籤屬性的名稱。
    required:指明該屬性是否爲必須屬性,該子元素的值是truefalse
    fragment:設置該屬性是否支持JSP腳本、表達式語言等動態內容,該子元素的值是truefalse

    配置完成,然後我們在頁面中使用標籤tag2,在tagTest.jspbody部分,添加一行新代碼:
    <mytag:tag2 attr1="value1" attr2="value2" /><br/>
<mytag:tag2 attr1="value3" attr2="value4" /> 
運行程序,可以得到下面的結果:
     
可以看到頁面中顯示我們設定的屬性值。

·動態屬性標籤:
    在某些情況,我們可能會用到屬性名稱和屬性數目都不確定的標籤,這樣的標籤稱爲“動態屬性”標籤。要開發具有動態屬性的自定義標籤,需要滿足下面兩個額外的要求:
    ① 標籤處理類要實現DynamicAttributes接口。
    ② 配置標籤時通過<dynamic-attributes.../>子元素指定該標籤支持動態屬性。 
下面我們定義一個新的標籤tag3,首先在配置文件中添加配置信息如下:
<tag>
		<name>tag3</name>
		<tag-class>tag.Tag3</tag-class>
		<body-content>empty</body-content>
		<dynamic-attributes>true</dynamic-attributes>
</tag>


這裏需要爲<tag>元素添加一個新的子元素<dynamic-attributes>,設置該標籤支持動態屬性。
    然後添加標籤處理類Tag3,代碼如下:
package tag;

import java.io.IOException;
import java.util.HashMap;

import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspWriter;
import javax.servlet.jsp.tagext.DynamicAttributes;
import javax.servlet.jsp.tagext.SimpleTagSupport;

public class Tag3 extends SimpleTagSupport
		implements DynamicAttributes {
	HashMap<String, Object> attributes = new HashMap<>();
	
	@Override
	public void setDynamicAttribute(String uri,
			String localName, Object value)
			throws JspException {
		// TODO Auto-generated method stub
		attributes.put(localName, value);
	}

	public void doTag() throws IOException {
		JspWriter writer = getJspContext().getOut();
		for(String key : attributes.keySet()) {
			writer.write(key + " = " + attributes.get(key) + "<br/>");
		}
	}
}

Tag3類實現了DynamicAttributes接口,該接口中包含一個方法setDynamicAttribute用於處理用戶自定義的屬性。這裏,我們的操作是僅僅將屬性名稱和屬性值存儲在一個hashMap中,然後調用doTag方法,將屬性名的屬性值向頁面輸出。
    在tagTest.jsp頁面中添加如下代碼,使用tag3標籤:
        
<h3>Dynamic attributes tag test:</h3><hr>
        <mytag:tag3 attr1="value1" attr2="value2" />
        <mytag:tag3 attr3="value3" attr4="value4" attr5="value5" />
運行程序,看到下圖的結果: 
   
    可以看到頁面顯示屬性名和對應的屬性值。動態屬性標籤配置成功。

·以頁面片段爲屬性的標籤:
    JSP2 允許以一段“頁面片段”作爲屬性,這種方式給自定義標籤提供了更大的靈活性。要定義以頁面片段爲屬性的標籤,需要滿足以下要求:
    ① 標籤處理類中定義類型爲JspFragment的屬性,該屬性代表了“頁面片段”。
    ② 使用標籤庫時,通過<jsp:attribute.../>動作指定爲標籤庫屬性指定值。
    下面定義一個新的標籤tag4,該標籤以頁面片段爲屬性。首先在tld文件爲該標籤配置<tag>元素,如下:
<tag>
		<name>tag4</name>
		<tag-class>tag.Tag4</tag-class>
		<body-content>empty</body-content>
		<attribute>
			<name>fragment</name>
			<required>true</required>
			<fragment>true</fragment>
		</attribute>
</tag>

這裏需要注意的是,以頁面片段爲屬性的標籤不可以有標籤體,並且,屬性的<fragment>子元素的值爲true
    接下來是標籤處理類Tag4,代碼如下:
package tag;

import java.io.IOException;

import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.JspFragment;
import javax.servlet.jsp.tagext.SimpleTagSupport;

public class Tag4 extends SimpleTagSupport {
	JspFragment fragment;

	public JspFragment getFragment() {
		return fragment;
	}

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

	public void doTag() throws JspException,
			IOException {
		fragment.invoke(null);
	}
}

    Tag4類中有一個JspFragment類型的成員變量,代表該對應標籤的屬性時一段頁面片段,調用它的invoke方法,可以將頁面片段輸出到對應的頁面中,我們在doTag方法中實現了這一操作。
    然後在JSP頁面中使用該標籤,利用<jsp:attritude.../>動作指令設定標籤的屬性值。格式如下:
    <mytag:tag4>
        <jsp:attritude name="...">
            <屬性值>
        <jsp:attritude />
    <mytag:tag4>
    注意,由於我們配置標籤爲沒有標籤體的空標籤,因此<mytag:tag4>標籤中,除了<jsp:attritude>指令外,不能有其他內容,包括註釋。    
    這裏我們用一個新的JSP頁面來測試標籤tag4,代碼如下;
<%@ page language="java" contentType="text/html; charset=GBK"
    pageEncoding="GBK"%>
<%@taglib uri="abc" 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=ISO-8859-1">
		<title>Insert title here</title>
	</head>
	<body>
		<h3>Page Fragment as an Attribute:</h3><hr>
		<mytag:tag4>
			<jsp:attribute name="fragment">
				<mytag:tag1 />
			</jsp:attribute>
		</mytag:tag4>
		<br>
		<mytag:tag4>
			<jsp:attribute name="fragment">
				<mytag:tag2 attr1="v1" attr2="v2"/>
			</jsp:attribute>
		</mytag:tag4>
		
		<br><h3>Using Tag Body:</h3><hr>
		<mytag:tag5>
			<mytag:tag1 />
		</mytag:tag5>
		<mytag:tag5>
			<mytag:tag2 attr1="v1" attr2="v2"/>
		</mytag:tag5>
	</body>
</html>

    tagTest2頁面中,我們使用了2次tag4標籤,屬性分別傳入我們前文中定義的標籤tag1tag2,以下是輸出結果:
   

可以看到頁面輸出了作爲傳入參數的頁面片段的內容,表明標籤tag4配置成功。
    筆者認爲,以頁面片段爲屬性的標籤的功能和下文介紹的帶有標籤體的標籤的功能很相似,詳細說明見下文。

4、帶標籤體的標籤:
    
本節介紹帶有標籤體的自定義標籤,設計帶有標籤體的自定義標籤很簡單,只要在標籤處理類中的doTag方法中通過getJspBody方法就可以返回封裝了標籤體內容的對象,需要說明的是,該方法的返回值類型就是JspFragment,因此,帶有標籤體的標籤和以頁面片段爲屬性的標籤的功能是類似的。 
    我們設計一個帶標籤體的標籤tag5,首先標籤處理類代碼如下:

package tag;

import java.io.IOException;

import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.SimpleTagSupport;

public class Tag5 extends SimpleTagSupport {
	public void doTag() throws JspException,
			IOException {
		getJspBody().invoke(null);
	}
}

    Tag5類中先通過getJspBody方法獲得標籤體內容,然後用invoke方法相頁面輸出標籤體。功能和標籤tag4基本相同。
    接下來,在mytaglib.tld中配置tag5,添加如下的代碼:
<tag>
		<name>tag5</name>
		<tag-class>tag.Tag5</tag-class>
		<body-content>scriptless</body-content>
</tag>

    由於tag5中包含標籤體,因此,這裏<body-content>子元素的值爲scriptless  
    然後在tagTest2.jsp頁面中添加如下代碼:
                
<br><h3>Using Tag Body</h3><hr>
<mytag:tag5>
<mytag:tag1 />
</mytag:tag5>
<mytag:tag5>
<mytag:tag2 attr1="v1" attr2="v2"/>
</mytag:tag5>
運行頁面,結果如下:       

    可以看到tag5標籤配置成功,功能和tag4標籤類似。
    僅僅將標籤體的內容輸出到頁面中在實際項目開發中並沒有太大的意義,下面我們設計一個新的標籤tag6,來完成更復雜的功能。
    首先是標籤處理類Tag6,代碼如下:
package tag;

import java.io.IOException;
import java.util.Collection;

import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.SimpleTagSupport;

public class Tag6 extends SimpleTagSupport {
	private String collectionKey;
	private String elementKey;

	@SuppressWarnings("rawtypes")
	public void doTag() throws JspException,
			IOException {
		Collection collection = (Collection) getJspContext()
				.getAttribute(collectionKey);
		for (Object element : collection) {
			getJspContext().setAttribute(
					elementKey, element);
			getJspBody().invoke(null);
		}
	}

	public String getCollection() {
		return collectionKey;
	}

	public void setCollectionKey(
			String collectionKey) {
		this.collectionKey = collectionKey;
	}

	public String getElementKey() {
		return elementKey;
	}

	public void setElementKey(String elementKey) {
		this.elementKey = elementKey;
	}
}
    Tag6類的doTag方法中通過標籤設定的collectionKey值來讀取存儲在page範圍中的集合對象,然後通過循環將集合中包含的每一個元素通過elementkey放置在page中,然後反覆處理標籤體的內容。
    接下來是配置文件,基本和前文的描述大同小異: 
<tag>
		<name>tag6</name>
		<tag-class>tag.Tag6</tag-class>
		<body-content>scriptless</body-content>
		<attribute>
			<name>collectionKey</name>
			<required>true</required>
			<fragment>false</fragment>
		</attribute>
		<attribute>
			<name>elementKey</name>
			<required>true</required>
			<fragment>false</fragment>
		</attribute>
</tag>
     然後是JSP頁面:
<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
    pageEncoding="ISO-8859-1"%>
<%@ page import="java.util.*" %>
<%@ taglib uri="abc" 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=ISO-8859-1">
<title>Insert title here</title>
</head>
<body>
<%
	Set set = new HashSet();
	set.add(1);
	set.add(2);
	set.add(3);
	
	pageContext.setAttribute("collection", set);
%>
<h3>Iterator Tag:</h3><hr>
<mytag:tag6 collectionKey="collection" elementKey="element">
	${pageScope.element}<br>
</mytag:tag6>
</body>
</html>
    在tagTest3.jsp頁面中,我們將一個包含3個整數的集合放入page中,然後使用tag6,傳入表示集合和集合元素的key值,標籤會迭代地輸出集合中的每一個元素。運行效果如下:
      
    通過這個例子可以看到,我們使用自定義標籤實現了迭代處理的功能。


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