如何開發自定義標簽
一、簡介
原理:用戶自定義的 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; } //標簽體邏輯實際應該編寫到該方法中. @Overridepublic 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("<"); str = Pattern.compile(">").matcher(str).replaceAll(">"); 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 執行標簽體。
如何開發自定義標簽