自定義标簽
自定義标簽入門
什麼是自定義标簽
自定義标簽可以有效地将HTML代碼與Java代碼分離,進而使不懂Java程式設計的HTML設計人員也可以編寫出功能強大的JSP頁面
JSP規範中定義了多個用于開發自定義标簽的接口和類,它們都位于javax.servlet.jsp.tagext包中
JSP自定義标簽是使用者定義的JSP語言元素,可以看成是一種通過标簽處理器生成基于XML腳本的方法。
自定義标簽在功能上和邏輯上都與JavaBean類似,都是一組可重用的元件代碼。相較于JavaBean,自定義标簽可以使Web開發者可以完全從Java程式設計中脫離開來,專注于頁面顯示和格式上面去,是以具有廣闊的發展前景。
自定義标簽的構成
一個自定義标簽一般由JavaBean、标簽庫描述、标簽處理器、web.xml檔案配置、标簽庫聲明等元素所構成。
自定義标簽聲明
當JSP頁面中引用自定義标簽時,用來在頁面上對自定義标簽進行聲明的。
taglib編譯指令的作用主要是定義一個标簽庫路徑及其字首
<%@ taglib="URLToTagLibrary" prefix="tagPrefix"%>
标簽庫描述符檔案
是一個描述标簽庫的XML文檔
TLD包含有關整個庫以及庫中包含的每一個标簽的資訊。它把自定義标簽與對應的處理程式關聯起來。TLD檔案名稱必須擴充名為.tld。
TLD檔案存儲在Web子產品的WEB-INF目錄下或者子目錄下,并且一個标簽庫要對應一個标簽庫描述檔案,而在一個描述檔案中可以包含多個自定義标簽的聲明
<taglib>子元素
元素
說明
元素
說明
<tlib-version>
用于設定标簽庫版本
<small-icon>
用于設定标簽庫的可選小圖示
<jsp-version>
用于設定标簽庫要求的JSP規範版本
<large-icon>
用于設定标簽庫的可選大圖示
<short-name>
用于設定該标簽庫的助記名
<description>
用于設定标簽庫的描述資訊
<uri>
唯一辨別該标簽庫的URI
<listener>
用于設定标簽庫的監聽器類
<display-name>
用于設定标簽庫顯示的可選名
<tag>
用于設定标簽庫的具體标簽
真正用來查找标簽庫中具體标簽的是<tag>元素
<tag>子元素
元素
說明
元素
說明
<name>
用于設定标簽的唯一名稱
<small-icon>
用于設定标簽的可選小圖示
<tag-class>
用于設定标簽處理器的完全限定名
<large-icon>
用于設定标簽的可選大圖示
<tei-class>
用于設定腳本變量資訊的子類名稱
<description>
用于設定标簽的描述資訊
<body-content>
用于設定标簽的正文内容類型
<variable>
用于設定标簽的腳本變量資訊
<display-name>
用于設定标簽顯示的可選名
<attribute>
用于設定标簽的屬性資訊
标簽處理器
把自定義标簽的主體和屬性轉變為HTML代碼的實際工作,是由标簽處理器來完成的。
标簽處理器也叫标簽處理類,它是一個Java類。當JSP容器編譯自定義标簽時,就會需要使用标簽處理器類的執行個體。
标簽處理器雖然是一個Java類,但不僅僅是一個普通的Java類,在定義時需要滿足特殊的要求。開發的标簽處理類必須實作Tag或者BodyTag接口類(它們包為javax.servlet.jsp.tagext)
BodyTag接口是繼承了Tag接口的子接口。如果建立的自定義标簽不帶體式,可以實作Tag接口,但是如果建立的自定義标簽帶體,則需要實作BodyTag接口。
Tag接口類方法
方法名
方法描述
setPageContext(PageContext pc)
設定目前頁面的上下文
setParent(Tag t)
設定這個标簽處理類的父類
getParent()
獲得父類
doStartTag()
處理這個執行個體中的開發标簽
doEndTag()
處理這個執行個體中的結束标簽
release()
由标簽處理類引起,來釋放狀态
BodyTag子接口類重新定義了兩個新方法
方法名
方法描述
setBodyContent(BodyContent b)
為體中代碼作初始化
doInitBody()
為标簽體中的内容設定屬性
在标簽處理器中定義了标簽處理方法doStartTag()和doEndTag(),這兩個方法分别在标簽開始和結束時執行處理和輸出動作。
這兩個方法都要求分别傳回一個狀态碼,通知JSP容器歲自定義标簽的處理結果及整個JSP頁面的運作狀态
狀态碼一共有四種
生命周期
生成servlet需要建立标簽處理器類的一個執行個體
初始化标簽處理器,是servlet獲知其存在性
如果标簽具有屬性,屬性的取值通過處理器提供setter方法傳入到對象
調用标簽處理器的doStrartTag()方法并傳回一個整數值
标簽體被評估或忽略後調用标簽處理器的doEndTag()方法
調用标簽處理器的release()方法銷毀标簽處理器執行個體
自定義标簽的開發步驟
1、編寫标簽處理器
開發自定義标簽的核心任務就是要編寫作為标簽處理器的Java類。
傳統标簽開發
實作javax.servlet.jsp.tagext.Tag接口
調用doStartTag()方法
簡單标簽開發
實作javax.servlet.jsp.tagext.SimpleTag接口
調用doTag()方法
2、編寫标簽庫描述符(tld)檔案
要想讓JSP引擎在遇到自定義标簽時,能找到其所對應的标簽處理器類,還必須編寫一個标簽庫描述符(Tag Library Descriptor)檔案,簡稱TLD檔案。
一個标簽處理器類要想被JSP容器找到并調用,必須在TLD檔案中進行注冊, 一個TLD檔案中可以注冊多個标簽處理器類,每個自定義标簽的注冊名稱不能相同,同一個TLD檔案中注冊的多個标簽處理器類就形成了一個自定義标簽庫。
TLD檔案是基于XML檔案的,其内容的編寫需要遵循XML文法規範。
3、在JSP頁面導入和使用自定義标簽
TLD檔案編寫完成後,就可以在JSP檔案中使用自定義标簽。在使用自定義标簽之前,首先需要使用taglib指令來引入TLD檔案
<%@taglib uri = "" prefix = "" %>
uri屬性用于指定引用的是哪一個TLD檔案,它應該和要引入的TLD檔案中<uri>元素的值保持一緻。
prefix屬性用于為引入的TLD檔案指定一個“引用代号”,在使用這個标簽庫中注冊的自定義标簽時都需要加上這個“引用代号”作為字首。prefix屬性的值可以是任意的,但不能和其它taglib指令中的prefix屬性值重複,而且需要遵循XML名稱空間的命名約定。
自定義标簽的格式
空标簽
<prefix:tagname /> // 格式1
<prefix:tagname></prefix:tagname> // 格式2
帶标簽體的标簽
<prefix:tagname>body</prefix:name>
帶屬性的标簽
<prefix:tagname attrname1 = "attrvalue1" [attrname2 = "attrvalue2" …]>
[body]
</prefix:tagname>
嵌套标簽
<prefix:tagname>
<prefix:nestedtagname>
[body]
</prefix:nestedtagname>
</prefix:tagname>
傳統标簽
實作Tag接口的标簽稱為傳統标簽
Tag接口
是所有傳統标簽的父接口,它定義了四個int類型的靜态常量和六個抽象方法。
定義了JSP頁面與标簽處理器之間的通信規則,當JSP容器将JSP頁面翻譯成Servlet源檔案時,如果遇到JSP标簽,會建立标簽處理器類的執行個體對象,然後依次調用标簽處理器的setPageContext()方法、setParent()方法、doStartTag()方法、doEndTag()方法和release()方法,是以,在實作Tag接口時,需要對這些抽象方法進行實作。
靜态常量
EVAL_BODY_INCLUDE
doStartTag()方法的傳回值,表示标簽體會被執行
SKIP_BODY
doStartTag()方法的傳回值,表示标簽體不被執行
EVAL_PAGE
doEndTag()方法的傳回值,表示标簽後面餘下的JSP頁面繼續執行
SKIP_PAGE
doEndTag()方法的傳回值,表示标簽後面餘下的JSP頁面不被執行
抽象方法
void setPageContext(PageContext pc)
JSP容器執行個體化标簽處理器後,調用setPageContext()方法将JSP頁面的内置對象pageContext對象傳遞給标簽處理器,标簽處理器可以通過pageContext對象與JSP頁面進行通信
void setParent(Tag t)
調用setPageContext()方法後,JSP容器會調用setParent()方法将目前标簽的父标簽處理器對象傳遞給目前标簽處理器,如果目前标簽沒有父标簽,則傳遞給setParent()方法的參數為null
Tag getParent()
傳回目前标簽的父标簽處理器對象,如果目前标簽沒有父标簽則傳回null
int doStartTag()
當JSP容器解析到自定義标簽的開始标簽時,會調用doStartTag()方法,該方法可以傳回EVAL_BODY_INCLUDE和SKIP_BODY兩個常量,如果使用Tag的子接口BodyTag,還可以使用BodyTag.EVAL_BODY_BUFFERED常量。
int doEndTag()
當JSP容器解析到自定義标簽的結束标簽時,會調用doEndTag()方法,該方法可以傳回EVAL_PAGE和SKIP_PAGE兩個常量
void release()
JSP容器在标簽處理器對象被作為垃圾回收之前調用release()方法,以便釋放标簽處理器所占用的資源
IterationTag接口
對标簽體的内容進行重複處理
它繼承自Tag接口,在Tag接口基礎上新增了一個EVAL_BODY_AGAIN常量和一個doAfterBody()方法
EVAL_BODY_AGAIN常量
是doAfterBody()方法的傳回值,如果doAfterBody()方法傳回該常量,JSP容器會把标簽體的内容重複執行一次。
doAfterBody()方法
JSP容器在每次執行完标簽體後會調用doAfterBody()方法,該方法可以傳回常量SKIP_BODY和EVAL_BODY_AGAIN。如果方法傳回SKIP_BODY常量,JSP容器會去執行代表結束标簽的doEndTag()方法,如果傳回EVAL_BODY_AGAIN,則重複執行标簽體。
BodyTag接口
對标簽體的内容進行處理以後再向浏覽器輸出
它繼承自IterationTag接口,在IterationTag接口基礎上新增了兩個方法和一個靜态常量
EVAL_BODY_B9UFFERED常量
如果标簽處理器類實作了BodyTag接口,它的doStartTag()方法除了可以傳回SKIP_BODY和EVAL_BODY_INCLUDE常量之外,還可以傳回EVAL_BODY_BUFFERED常量。
當doStartTag()方法傳回EVAL_BODY_BUFFERED常量時,JSP容器将會建立一個javax.servlet.jsp.tagext.BodyContent對象,使用該對象來執行标簽體。
setBodyContent(BodyContent b)方法
當且僅當doStartTag()方法傳回EVAL_BODY_BUFFERED常量時,JSP容器才會調用setBodyContent()方法,通過該方法将BodyContent對象傳遞給标簽處理器類使用。
doInitBody()方法
JSP容器在調用setBodyContent()方法後會調用doInitBody()方法來完成一些初始化工作,該方法的調用在标簽體執行之前。其中,最重要的是setBodyContent()方法。
BodyContent類
是JspWriter類的子類,它在JspWriter的基礎上增加了一個用于存儲資料的緩沖區,當調用BodyContent對象的方法寫資料時,資料将被寫入到BodyContent内部的緩沖區中。
當标簽處理器類的doStartTag()方法傳回EVAL_BODY_BUFFERED常量時,JSP容器會建立一個BodyContent對象,然後調用該對象的write()方法将标簽體的内容寫入BodyContent對象的緩沖區中。
在BodyContent類中定義了一些用于通路緩沖區内容的方法
String getString()
以字元串的形式傳回BodyContent對象緩沖區中儲存的資料
Reader getReader()
傳回一個關聯BodyContent對象緩沖區中資料的Reader對象,通過Reader對象可以讀取緩沖區中的資料
void clearBody()
用于清空BodyContent對象緩沖區中的内容
JspWriter getEnclosingWriter()
用于傳回BodyContent對象中關聯的JspWriter對象。當JSP容器建立BodyContent對象後,PageContext對象中的“out”屬性不再指向JSP的隐式對象,而是指向新建立的BodyContent對象。同時,在BodyContent對象中會用一個JspWriter類型的成員變量enclosingWriter記住原來的隐式對象,getEnclosingWriter()方法傳回的就是原始的JSP隐式對象
writerOut(Writer out)
用于将BodyContent對象中的内容寫入到指定的輸出流
簡單标簽
實作SimpleTag接口的标簽稱為簡單标簽
由于傳統标簽在使用三個标簽接口來完成不同的功能時,顯得過于繁瑣,不利于标簽技術的推廣。
SimpleTag接口與傳統标簽接口最大的差別在于:SimpleTag接口中隻定義了一個用于處理标簽邏輯的doTag方法,該方法用于取代傳統标簽接口中定義的doStartTag()、doEndTag()和doAfterBody()等方法。
doTag()方法在JSP引擎執行自定義标簽時調用,并且隻被調用一次,那些使用傳統标簽接口所能完成的功能,都在doTag()方法體内完成。
簡單标簽API
SimpleTag接口
是所有簡單标簽處理器的父接口,它共定義了5個方法
void setJspContext(JspContext pc)
用于将JSP頁面的内置對象pageContext對象傳遞給标簽處理器,标簽處理器可以通過pageContext對象與JSP頁面進行通信。JSPContext類是PageContext類的父類,其中定義了一些不依賴于Servlet運作環境的方法,setJspContext()方法接收的參數類型為JspContext,是為了便于将簡單标簽擴充應用到非Servlet運作環境中
void setParent(JspTag parent)
用于将目前标簽的父标簽處理器對象傳遞給目前标簽處理器,如果目前标簽沒有父标簽,JSP容器不會調用這個方法
JspTag getParent()
傳回目前标簽的父标簽處理器對象,如果目前标簽沒有父标簽則傳回null
void setJspBody(JspFragment jspBody)
用于把代表标簽體的JspFragment對象傳遞給标簽處理器對象
void doTag()
用于完成所有的标簽邏輯,包括輸出、疊代、修改标簽體内容等。在doTag()方法中可以抛出javax.servlet.jsp.SkipPageException異常,用于通知JSP容器不再執行JSP頁面中位于結束标簽後面的内容,這等效于在傳統标簽的doEndTag()方法中傳回SKIP_PAGE常量
JspFragment類
它的執行個體對象代表JSP頁面中一段JSP片段,但是這段JSP片段中不能包含JSP腳本元素
JSP容器在處理簡單标簽的标簽體時,會把标簽體内容用一個JspFragment對象表示,并調用标簽處理器對象的setJspBody()方法将JspFragment對象傳遞給标簽處理器對象,标簽開發者可以根據需要調用JspFragment對象的方法來決定是否輸出标簽體、或者循環多次輸出标簽體等
兩個方法
JspContext getJspContext()
用于傳回代表調用頁面的JspContext對象
void invoke(Writer out)
用于将标簽體内容寫入到指定的輸出流對象out 中,如果調用該方法時傳入的參數為null,JSP容器會将标簽内容寫入到JspContext.getOut()方法傳回的輸出流對象中。
SimpleTagSupport類
為了簡化簡單标簽處理器的編寫
該類實作了SimpleTag接口,它内部使用成員變量jspContext和jspBody引用了JSP容器傳入的JspContext對象和JspFragment對象,并且提供了兩個方法來傳回這兩個對象的引用
JspContext getJspContext()
用于傳回代表調用頁面的JspContext對象
JspFragment getJspFragment()
用于傳回代表标簽體的JspFragment對象
控制是否執行标簽體内容
簡單标簽使用doTag()方法完成判斷,如果使用者登入,則顯示使用者名稱,如果使用者沒有登入,則可以抛出javax.servlet.jsp.SkipPageException異常,用于通知JSP容器不再執行标簽體内容,這等效于在傳統标簽的doEndTag()方法中傳回SKIP_PAGE常量。
控制是否執行JSP頁面的内容
假設一個網站要求隻能通過本網站中的超連結來通路某些JSP頁面時,如果直接通路這些JSP頁面或者通過非本網站的超連結來通路這些JSP頁面,那麼被通路的JSP頁面應該停止執行其中的内容,此現象稱為防盜鍊。
簡單标簽的屬性
如果多個JSP頁面都需要防盜鍊功能,則需要将使用者的通路請求重定向到不同的頁面。
這時,為了提高标簽的靈活性和複用性,在JSP頁面使用自定義标簽時,可以通過設定屬性為标簽處理器傳遞參數資訊。
例如,可以為<itcast:antiHotLinking />标簽增加一個url屬性,通過該屬性指定重定向的頁面
<itcast:antiHotLinking url = "/chapter09/index.html" />
要想為自定義标簽設定屬性,通常需要完成兩件任務,具體如下:
1、在标簽處理器類中,為每一個屬性定義對應的成員變量并定義setter()方法
自定義标簽每個屬性都必須按照JavaBean的屬性定義方式,預設情況下,自定義标簽的屬性值為靜态文本,當setter()方法的參數為String類型時,JSP容器會直接将屬性值作為一個字元串傳遞給該方法。如果setter()方法中的參數類型為基本資料類型,JSP容器在調用setter()方法之前會先将屬性值進行轉換。
例如在JSP頁面中使用了如下所示的标簽:
<itcast:test attr = "123" />
而标簽處理器類中定義的setter方法如下所示:
public void setAttr(int attr)
當JSP容器在處理attr屬性時,會先調用Integer. value("123")方法将字元串“123”轉換為整數123,之後再作為參數傳遞給标簽處理器類的setAttr()方法。
2、在TLD檔案中聲明每個标簽的屬性資訊
在TLD檔案中,<tag>标簽有一個子元素<attribute>用于描述自定義标簽的屬性,自定義标簽的每個屬性都必須要有一個對應的<attribute>元素
在<attribute>元素中還包含了一些子元素
description
用于描述屬性的描述資訊
name
用于指定屬性的名稱,屬性名大小寫敏感,且不能以jsp、_jsp、java、和sun開頭
required
用于指定在JSP頁面調用自定義标簽時是否必須設定這個屬性。其取值包括true和false,true表示必須設定,false表示設不設定均可。預設值為false
rtexprvalue
rtexprvalue是runtime expression value(運作時表達式)的簡稱,用于指定屬性的值為靜态還是動态。其取值包括true和false,true表示屬性值可以為一個動态元素,比如一個腳本表達式<%=value%>,false表示屬性值隻能為靜态文本值,比如“abc”。預設值為false
type
用于指定屬性值的類型