同JSP標簽一樣,自定義標簽主要用於移除JSP頁面中的Java代碼,可以看到我們在JSP中其實是禁止使用Java腳本的,任何要想通過Java代碼實現的功能都必須以標簽形式來處理,可以使用JSP標簽,JSTL標簽,EL函數,或者自定義標簽。
自定義標簽分為傳統標簽和簡單標簽,簡單標簽是Sun公司為減低自定義標簽技術的學習難度而定義的,對於簡單標簽請看下一篇博客。本文先來學習傳統自定義標簽。
使用傳統自定義標簽需要滿足以下兩個步驟:
① 編寫一個實現Tag接口(實際上我們更常的是繼承Tag接口的實現類從而免於覆寫所有的方法)的Java類,這個Java類也稱為標簽處理器類。
② 編寫標簽的TLD文件,用於指定標簽的URI和對標簽的聲明描述,這一點和EL函數時一樣的,TLD文件必須放置在web應用下的【WEB-INF】文件中,可以是除【classes】和【lib】目錄以外的任何子目錄中。TLD文件的模板可以從【Tomcat】--->【webapps】--->【examples】--->【WEB-INF】--->【jsp2】中有一個“jsp2-example-taglib.tld”文件復制首尾和其中的<Tag>標簽。
注:在TLD文件中我們使用<tag>標簽來對每一個自定義標簽的Java類進行描述,其中每個<tag>標簽下還需要指定<body-content>的值,這個是描述標簽體的類型:
對於傳統標簽使用:“EMPTY”(代表標簽沒有標簽體)或“JSP”(代表標簽有標簽體)。
以上的步驟整體類似於我們自定義EL函數,而不同的是這里的Java類需要繼承特定的JSP的API中提供的類並覆蓋其中特定的方法。
我們先看一看Tag類的API,注意,這是JSP技術,請看JSP的API:


從上面可以看到Tag是一個接口,如果我們直接繼承Tag接口的話,那就得覆寫這六個方法,那就顯得十分麻煩了,況且有些方法是屬於標簽的生命周期方法,是由JSP引擎調用的,因此我們只需要繼承Tag接口的實現子類即可,通常我們使用“TagSupport”類或者“BodyTagSupport”類,能有更多的功能。
在Tag接口中,除了getParent方法以外,其他都是生命周期方法。在瀏覽器在解析一個JSP頁面時,遇到某個自定義標簽后就會開始執行該標簽的生命周期方法。自定義標簽的生命周期順序為:① 創建自定義標簽的實例對象 ---> ② setPageContext方法 ---> ③ setParent方法---> ④ doStart方法 ---> ⑤ doEnd方法 ---> ⑥ release方法(通常在服務器關閉時才調用)。
下面簡單的介紹下這幾個生命周期方法:
setPageContext方法(重要),JSP引擎對標簽進行實例化對象后,會先調用setPageContext方法,將JSP頁面的pageContext對象傳入這個標簽處理器類,我們在JSP的pageContext隱式對象一文中說過,只要擁有了pageContext對象,那么就可以獲取其他八大隱式對象從而操作web中的需求了。
setParent方法,在setPageContext方法執行完之后,將調用setParent方法,這個方法將會把這個自定義標簽的父類標簽(如果有)傳遞給該標簽處理器類,如果沒有父類標簽,那么setParent方法的參數即為null。注意,這里說的標簽的父標簽也是指自定義標簽,如果該自定義標簽的只是嵌入在普通的HTML標簽的話那么就是無父類標簽,執行的只是setParent(null)方法。
doStartTag方法,當JSP引擎接連調用setPageContext方法和setParent方法完成標簽的配置之后,當瀏覽器解析標簽的開始標簽時,就會調用doStartTag方法。通常我們在使用標簽處理某個功能時,就將該功能在doStartTag方法中覆寫。另外,依據doStartTag方法的返回值是“EVAL_BODY_INCLUDE”(執行)還是“SKIP_BODY”(不執行)決定是否執行標簽體中的內容。可以說這是我們要使用標簽來封裝Java代碼最重要的一個方法。
doEndTag方法,當瀏覽器在JSP頁面中解析到該標簽的結束標簽時,就會調用doEndTag方法。另外,依據doEndTag方法的返回值是“EVAL_PAGE”(執行)還是“SKIP_PAGE” (不執行)決定是否執行結束標簽之后余下的JSP頁面內容。
release方法,通常JSP調用完doEndTag方法后,並不會立即執行release方法,因為為了服務器的性能,通常就會將標簽處理器類的對象駐留於內存中,以便下次能更快速地調用,這一點和Servlet是一樣的。一般在停止該web應用或服務器停止時才會調用標簽處理器的release方法,釋放標簽中的資源。
通常我們使用的是一個Java類繼承TagSupport類或者BodyTagSupport類,對於處理標簽,依然也是覆寫doStartTag方法。同時注意到TagSupport類中,一個屬性即為pageContext,注意這是字段,同時能給子類調用(protected修飾),而我們基本在覆寫doStartTag方法中要隨處用到這個字段:

例1:使用自定義標簽來顯示來訪者IP
創建一個Java類繼承TagSupport類,覆寫doStartTag方法,這里因為標簽沒有標簽體,可以暫時不用管doStartTag方法的返回值:
1 package com.fjdingsd.tag; 2 public class GuestIpTag extends TagSupport { 3 @Override 4 public int doStartTag() throws JspException { 5 6 HttpServletRequest request = (HttpServletRequest) this.pageContext.getRequest(); 7 String ip = request.getRemoteAddr(); 8 JspWriter out = this.pageContext.getOut(); 9 try { 10 out.write(ip); 11 } catch (IOException e) { 12 throw new RuntimeException(e); 13 } 14 return super.doStartTag(); 15 } 16 }
接着在web應用的【WEB-INF】中創建TLD文件,設置好uri和標簽的名稱、類、以及標簽體類型:
1 <?xml version="1.0" encoding="UTF-8" ?> 2 3 <taglib xmlns="http://java.sun.com/xml/ns/j2ee" 4 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 5 xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd" 6 version="2.0"> 7 <description>A tag library exercising SimpleTag handlers.</description> 8 <tlib-version>1.0</tlib-version> 9 <short-name>SimpleTagLibrary</short-name> 10 <uri>selftag</uri> 11 12 <tag> 13 <name>guestip</name> 14 <tag-class>com.fjdingsd.tag.GuestIpTag</tag-class> 15 <body-content>empty</body-content> 16 </tag> 17 </taglib>
最后就可以在JSP頁面中使用自定義標簽了,當然別忘了使用taglib指令先導入我們的標簽所在的uri:
<%@ taglib uri="selftag" prefix="selftag" %>
在JSP頁面的主體中使用我們設計好的自定義標簽:
您的ip地址為:<selftag:guestip/>
瀏覽器中觀察:

例2:控制標簽的標簽體內容是否輸出顯示
如果要想控制標簽體的內容是否輸出顯示,只需要修改doStartTag方法的返回值即可:
1 package com.fjdingsd.tag; 2 public class ShowTagBodyOrNot extends TagSupport { 3 @Override 4 public int doStartTag() throws JspException { 5 HttpSession session = this.pageContext.getSession(); 6 User user = (User) session.getAttribute("user"); 7 if(user==null) { 8 return TagSupport.SKIP_BODY; //隱藏標簽體內容 9 }else{ 10 return TagSupport.EVAL_BODY_INCLUDE; //顯示標簽體內容 11 } 12 } 13 }
在TLD文件中定義(這里忽略文件首尾其他定義):
1 <tag> 2 <name>showbody</name> 3 <tag-class>com.fjdingsd.tag.ShowTagBodyOrNot</tag-class> 4 <body-content>JSP</body-content> 5 </tag>
在JSP頁面中導入taglib指令后並在JSP頁面主體部分使用自定義標簽:
1 <selftag:showbody> 2 只有登錄用戶才能顯示…… 3 </selftag:showbody>
當然這個例子當我沒有在session域中存入User對象時,是不會在頁面上顯示這個標簽的標簽體內容的,這里只是用來強調在doStartTag方法的返回值“EVAL_BODY_INCLUDE”與“SKIP_BODY”的區別。
例3:控制標簽之后余下的JSP頁面是否輸出顯示
如果要想標簽之后余下的JSP頁面是否輸出顯示,只需要修改doEndTag方法的返回值即可:
1 package com.fjdingsd.tag; 2 public class ShowJSPOrNot extends TagSupport { 3 //注意,TagSupport的doStartTag方法默認返回值為SKIP_BODY,也就是不執行標簽體內容 4 @Override 5 public int doEndTag() throws JspException { 6 HttpSession session = this.pageContext.getSession(); 7 User user = (User) session.getAttribute("user"); 8 if(user==null) { 9 return TagSupport.SKIP_PAGE; 隱藏結束標簽后余下JSP頁面 }else{ 10 return TagSupport.EVAL_PAGE; //顯示結束標簽后余下JSP頁面 11 } 12 } 13 }
在TLD文件中定義(這里忽略文件首尾其他定義):
1 <tag> 2 <name>showpage</name> 3 <tag-class>com.fjdingsd.tag.ShowJSPOrNot</tag-class> 4 <body-content>empty</body-content> 5 </tag>
在JSP頁面中導入taglib指令后並在JSP頁面主體部分使用自定義標簽:
1 <selftag:showpage/> 2 3 <!DOCTYPE HTML> 4 <html> 5 <head> 6 <title>My JSP 'demo1.jsp' starting page</title> 7 </head> 8 9 <body> 10 。。。 11 <body> 12 </html>
這里我把自定義標簽置於JSP頁面最開始的地方,可以看到如果沒有在session域中存入User對象的話,那么這個JSP在被訪問后是不會看到任何東西的,查看網頁源碼也是沒有任何代碼。當然這個例子只是用來強調在doEndTag方法的返回值 “EVAL_PAGE”和 “SKIP_PAGE”的區別。
使用傳統的自定義標簽還可以擴展一些其他的功能,比如控制標簽體的內容重復執行,修改標簽體內容再輸出等等,這兩個功能涉及到使用Tag不同實現類的使用,將在下一篇博客中進行講解。
