JSP2開發自定義标簽
目錄
- JSP2開發自定義标簽
- 目錄
- 開發自定義标簽的步驟
- 開發簡單的自定義标簽
- 自定義标簽處理類
- 标簽處理類的聲明周期
- 建立TLD檔案
- 注冊标簽
- 使用标簽庫
- 帶屬性的标簽
- 帶有标簽體的标簽
- 以頁面片段作為屬性标簽
- 動态屬性标簽
- 源碼
1. 開發自定義标簽的步驟
JSP2的規範簡化了标簽庫的開發,在原本的JSP1.1中開發自定義标簽比較複雜。
JSP2中開發自定義标簽隻需要如下的幾步:
- 開發自定義标簽處理類。
- 建立一個*.tld檔案,每個檔案對應一個标簽庫,每個标簽庫可以包含多個标簽。
- 在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. 标簽處理類的聲明周期
- JSP容器通過調用其無參構造,建立一個簡單的标簽處理類執行個體。是以,簡單的标簽處理器必須有一個無參構造器。
- JSP容器調用setJspContext方法,同時傳遞一個JSPContext對象。JspContext最重要的方法時getOut,它傳回一個JspWriter,用于将相應發送到用戶端。setJspContext方法的簽名如下:
大多數時候,會需要将傳進的JspContext付一個類變量,以便後續使用。public void setJspContext(JspContext jspContext)
- 如果表示标簽處理類的定制标簽是嵌套在另一個标簽中的,JSP容器就會調用setParent方法。
- JSP容器為改标簽的定義的每個屬性都調用設定方法(setter)
- 如果标簽中有主體内容,JSP将調用SimpleTag接口的setJspBody方法,将主體内容作為JspFragment。如果沒有主體内容,則JSP容器不會調用這個方法。
- 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,這是一個可以做測試的一個域名。
打開之後:
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsICdzFWRoRXdvN1LclHdpZXYyd2LcBzNvwVZ2x2bzNXak9CX90TQNNkRrFlQKBTSvwFbslmZvwFMwQzLcVmepNHdu9mZvwFVywUNMZTY18CX052bm9CX9U1RaZHeHNGNW12YoJ1MjZXUYpVd1kmYr50MZV3YyI2cKJDT29GRjBjUIF2LcRHelR3LcJzLctmch1mclRXY39jN3gzNzUDMwITMyATM3EDMy8CX0Vmbu4GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.jpg)
翻譯過來大概是:該域被建立用于文檔中的說明性示例。您可以在沒有事先協調或要求許可的情況下使用此域。
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頁面中使用标簽庫需要注意如下兩點:
- 标簽庫的URI:确定使用哪個标簽庫。
- 标簽名:确定使用那個标簽。
使用标簽庫導入标簽庫,使用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>
運作之後可以看到如下效果:
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标準動作指令。
下面的是他的效果:
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>
下面是該便簽的測試效果圖:
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…/>子元素,甚至注釋都不行。
效果如下圖所示:
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>
下面是該标簽的效果:
7. 源碼
想要獲得源碼點選下方按鈕:
按鈕