Servlet&JSP 第八章 自定義標籤

一、Tag自定義標籤

1、Tag File簡介

(1)Tag File的*.tag文件放在/WEB-INF/tags文件夾或子文件夾,並在JSP中使用taglib指示元素的tagdir屬性指定*.tag的位置,就可以使用這個Tag File了。

(2)tag指示元素用來告知容器如何轉化這個Tag File。description屬性只是一段文字描述,用來說明這個Tag File的作用。pageEncoding屬性告知容器在轉譯Tag File時使用的編碼。Tag File中可以使用taglib指示元素引用其他自定義標籤庫,可以在Tag File中使用JSTL。基本上JSP文件中可以使用的EL或Scriptlet在Tag File中也可以使用。

例1、<%@tag description=”顯示錯誤信息的標籤” pageEncoding=”UTF-8” %>

(3)在需要這個Tag File的JSP頁面中,可以使用taglib指示元素的prefix屬性定義前置名稱。tagdir屬性定義Tag File的位置,tagdir只能指定/WEB-INF/tags的子文件夾,Tag File就只能放在/WEB-INF/tags或子文件夾中。

例2、<%@taglib prefix=”html” tagdir=”/WEB-INF/tags” %>

(4)Tag File中可以使用out、config、request、response、session、application、jspContext等隱式對象。Tag File在JSP中並不是靜態包含或動態包含,若在Tag File中編寫Scriptlet,其中的隱式對象其實好就是轉譯後.java中的doTag()方法中的局部變量。在Tag File中的Scriptlet定義的局部變量,也會是doTag()中的局部變量,所以也不可能與JSP的Scriptlet溝通。

2、處理標籤屬性與Body

(1)在創建Tag File時,可以通過attribute指示元素來指定使用某些屬性。Attribute只是原屬定義使用Tag File時可以設置的屬性名稱,設置名稱之後,若有人使用Tag File時指定屬性值,則這個值在*.tag文件中。

(2)<jsp:doBody/>標籤可以取得使用Tag File標籤時的Body內容。

(3)<html>與</html>的Body中,可以編寫想要的HTML、EL或自定義標籤,Body的內容會在<jsp:doBody/>位置與其他內容結合在一起。

(4)Tag File標籤在使用時若有Body,默認是不允許有Scriptlet的,因爲定義Tag File時,tag指示元素的body-content屬性默認是scriptless,也就是不可以出現<% %>、<%= %>或<%! %>元素。body-content屬性還可以設置empty或tagdependent,empty表示一定沒有Body內容,也就是隻能以<html:Header/>這樣的方式來使用標籤(非empty設置時,可以用<html:Header/>,或者是<html:Header>Body</html:Header>的方式),tagdependent表示將Body中的內容當作純文字處理,也就是如果Body中出現了Scriptlet、EL或自定義標籤,也就是當作純文字輸出,不會做任何的運算與轉譯。

3、TLD文件

(1)Tag File包裝成JAR文件,需要注意:

*.tag文件必須放在META-INF/tags文件夾或子文件夾下;

要定義TLD文件;

TLD文件必須放在JAR文件的META-INF/TLDS文件夾下;

(2)每個<tag-file>中使用<name>定義了自定義標籤的名稱,使用<path>定義了*.tag在JAR文件中的位置。

二、Simple Tag自定義標籤

1、Simple Tag簡介

所有要實現的內容都在doTag()中進行。

(1)開發Simple Tag:首先要編寫標籤處理器,這是一個Java類,可以繼承javax.servlet.jsp.tagext.SimpleTagSupport來實現標籤處理器,並重新定義doTag()方法來進行標籤處理。

(2)<f:if>標籤:<f:if>標籤如果有test屬性,標籤處理器必須有個接受test屬性的設值方法,再重新定義的doTag()方法中,如果test屬性爲true則調用SimpleTagSupport的getJspBody()方法,這會返回一個JspFragment對象,代表<f:if>與</f:if>間的Body內容。如果調用JspFragment的invoke()方法並傳入一個null,表示執行<f:if>與</f:if>間的Body內容,如果內有調用invoke(),則<f:if>與</f:if>間的Body內容不會被執行,也就不會有結果輸出至用戶的瀏覽器。

(3)爲了讓Web容器瞭解<f:if>標籤與IfTag標籤處理器之間的關係,要定義一個標籤程序庫描述文件,也就是一個後綴爲*.tld的文件。

(4)每個<tag>標籤中使用<name>定義了自定義標籤的名稱,使用<tag-class>定義標籤處理器類,而<body-content>設置爲scriptless,表示標籤Body中不允許使用Scriptlet等元素。

(5)如果標籤上有屬性,則使用<attribute>來設置,<name>設置屬性名稱,<required>表示是否一定要設置這個屬性。<rtexprvalue>表示屬性是否接受運行時的運算的結果(如EL表達式的結果),如果設置爲false或不設置<rtexprvalue>,表示在JSP上設置屬性時僅接受字符串形式。<type>設置屬性類型。

(6)可以將TLD文件放在WEB-INF文件夾下,這樣容器會自動加載它,如果要使用這個標籤,必須在JSP頁面上使用taglib指示元素。

2、瞭解API架構與生命週期

(1)JSP自定義Tag都實現了JspTag接口,JspTga接口只是個標示接口,本身沒有定義任何方法。SimpleTag接口繼承了JspTag,定義了SimpleTag開發時所需的基本行爲,開發Simple Tag標籤處理器時必須實現SimpleTag接口。

(2)JSP網頁中包括Simple Tag自定義標籤,若用戶請求該網頁,在遇到自定義標籤時,會按照以下步驟來處理:

創建自定義標籤處理器實例;

調用標籤處理器的setJspContext()方法設置PageContext實例;

如果是嵌套標籤中的內層標籤,則還會調用標籤處理器的setParent()方法,並傳入外層標籤處理器的實例;

設置標籤處理器實例;

調用標籤處理器的setJspBody()方法設置JspFragment實例;

調用標籤處理器的doTag()方法;

銷燬標籤處理器實例;

(3)每一次的請求都會創建新的標籤處理器實例,而在執行dotag()後就銷燬實例,在SimpleTag的實現中,建議不要有一些耗資源的動作。

(4)由於標籤處理器中被設置了PageContext,所以可以用它來取得JSP頁面的所有對象,進行所有在JSP頁面中可以執行的操作,之後就可以用自定義標籤來取得JSP頁面上的Scriptlet。

(5)JspFregment是個JSP頁面中的片段內容,在JSP中使用自定義標籤時若包括Body,將會轉譯爲一個JspFragment實現類,而Body內容將會在invoke()方法進行處理。

(6)如果doTag()的過程在某些條件下,必須中斷接下來頁面的處理或輸出,則可以拋出javax.servlet.jsp.SkipPageException,這個異常對象會在JSP轉譯後的_jspService()中進行處理。

3、處理標籤屬性與Body

(1)<f:forEach>標籤可以設置var屬性來決定每次從Collection取得對象時,應該用哪個名稱在標籤Body中取得該對象,var只接受字符串方式來設置名稱。

<f:forEach>標籤Body內容必須執行多次,則是通過多次調用invoke()方法來達成,也就是在doTag()中每調用一次invoke()則會執行依次Body內容。

通過SimpleTagSupport的getJspBody()取得JspFragment,並在調用invoke時傳入null,表示將使用PageContext取得默認的JspWriter對象來做輸出響應,也就是默認會輸出響應至用戶瀏覽器。

(2)如果調用invoke()設置了一個Writer對象,則會調用pageContext的pushBody()方法並傳入該對象,這會將pageContext的getOut()方法所取得的對象設置爲該Writer對象,並在堆棧中記錄先前的JspWriter對象。若標籤Body內容中還有內層標籤,通過getOut()取得的就是所設置的Writer對象(除非內層標籤在調用invoke()時,也設置了自己的Writer對象)。pushBody()返回的是BodyContent對象,爲JspWriter的子類,封裝了所傳入的Writer對象。因爲BadyContent實例被out引用,而運行結果都通過out所引用的對象輸出,所以最後BadyContent將會包括所有標籤Bady的運行結果(包括內層標籤),而這些結果,將再寫入BadyContent所封裝的Writer對象。

4、與父標籤溝通

(1)<f:when>標籤:test屬性查看是否執行Body內容。在測試開始前,必須先嚐試取得parent,如果無法取得(也就是爲null的情況),表示不在任何標籤之中;或是parent部位ChooseTag類型時,表示不是置於<f:choose>中,這是個錯誤的使用方式,所以必須拋出異常。如果確實是置於<f:choose>標籤中,接着嘗試取得parent的matched狀態,如果已經被設置爲true,表示先前有<f:when>已經通過設測試並執行了其Body內容,那麼目前這個<f:when>就不用再做測試了。如果是置於<f:choose>標籤中,而且先前沒有<f:when>通過測試,接着就可以進行目前這個<f:when>測試,如果測試成功,則設置parent的matched爲true,並執行標籤Body。

(2)<f:otherwise>標籤:必須確認是否置於<f:choose>標籤中,必須確認先前是否有<f:when>測試成功,如果先前沒有<f:when>測試成功,就直接執行標籤Body內容。

(3)如果在一個數個嵌套的標籤中,想要直接取得某個指定類型的外層標籤,則可以通過SimpleTagSupport的findAncestorWithClass()靜態方法,findAncestorWithClass()方法會在目前標籤的外層標籤中尋找,直到找到指定的類型的外層標籤對象後返回。

5、TLD文件

(1)TLD文件可以放在JAR文件的META-INF文件夾或子文件夾,也就是:

JAR文件根目錄下放置編譯好的類(包含對應包的文件夾);

JAR文件META-INF文件夾或子文件夾中放置TLD文件;

接着在文字模式中進入fake文件夾,運行命令:jar cvf../fake.jar *;

這樣在fake文件夾上一層目錄中,就回產生fake.jar文件,若想使用這個文件,只要將之置入WEB-INF/lib中,就可以開始使用自定義的標籤庫;

三、Tag自定義標籤

1、Tag簡介

(1)JSP中開始處理標籤時,會調用doStartTag()方法,後續是否執行Body則是根據doStartTag()的返回值決定,如果doStartTag()方法返回EVAL_BODY_INCLUDE常數(定義在Tag接口中),則會執行Body內容,若返回SKIP_BODY常數(定義在Tag接口中),則不執行Body內容。

(2)<body-content>可以設置的值有empty、JSP、tagdependent,JSP的設置值表示Body中若包括動態內容,如Scriptlet元素、EL或自定義標籤都會被執行。

(3)實現Tag接口相關的類時,按不同的時機,要定義不同的doXXXTag()方法,並按需求返回不同的值。

2、瞭解架構與生命週期

(1)doXXXTag()方法實際上是分別定義在Tag與IterationTag接口上的方法。

(2)Tag接口繼承自JapTag接口,它定義了基本的Tag行爲,如設置PageContext實例的setPageContext()、設置外層父標籤對象的setParent()方法、標籤對象銷燬前調用的release()方法等。

(3)單是使用Tag接口的話,無法重複執行Body內容,而必須使用子接口IterationTag接口的doAfterTag()。TagSupport類實現了IterationTag接口,對接口上所有方法做了基本實現,所以只需要在繼承TagSupport之後,針對必要的方法重新定義即可。

(4)JSP中遇到TagSupport自定義標籤時,會進行以下動作:

嘗試從標籤池找到可用的標籤對象,如果找到就直接使用,如果沒找到就創建新的標籤對象;

調用標籤處理器的setPageContext()方法設置PageContext實例;

如果是嵌套標籤中的內層標籤,則還會調用標籤處理器的setParent()方法,並傳入外層標籤處理器的實例;

設置標籤處理器實例;

調用標籤處理器的doStartTag()方法,並依不同的返回值決定是否執行Body或調用doAfterBody()、doEndTag()方法;

將標籤處理器實例置入標籤池中以便再次使用;

(5)Tag實例是可以重複使用的,所以自定義Tag類時,要注意對象狀態是否會保留下來,必要的時候,在doStartTag()方法中,可以進行狀態重置的動作,release()方法只會在標籤實例真正被銷燬回收前被調用。

(6)JSP頁面會根據標籤處理器各方法調用的不同返回值,來決定要調用哪一個方法或進行哪一個動作。

(4)doStartTag()可以回傳EVAL_BODY_INCLUDE或SKIP_BODY。如果返回EVAL_BODY_INCLUDE則會執行Body內容,而後調用doAfterTag()方法;如果返回SKIP_BODY則不執行Body內容,此時就會調用doEndTag()方法。

(5)doAfterTag()默認返回值是SKIP_BODY,如果不重新定義doAfterTag()方法,無論有無執行Body,流程最後都會來到doEndTag()。在doEndTag()中,可返回EVAL_PAGE或SKIP_PAGE,如果返回EVAL_PAGE,則自定義標籤後續的JSP頁面纔會繼續執行,如果返回SKIP_PAGE就不會執行後續的JSP頁面。

(6)由於TagSupport類對IterationTag接口做了基本實現,doStartTag()、doAfterBody()、doEndTag()都有默認的返回值,依序分別是SKIP_BODY、SKIP_BODY以及EVAL_PAGE,也就是默認不處理Body,標籤處理結束後會執行後續的JSP頁面。

3、重複執行標籤的Body

(1)doAfterBody()方法執行後,如果返回EVAL_BODY_AGAIN,則會重複執行一次Body內容,而後再次調用doAfterBody()方法,除非在doAfterBody()中返回SKIP_BODY纔會調用doEndTag()。當doStartTag()返回EVAL_BODY_INCLUDE後,會先執行Body內容再調用doAfterBody()方法,也就是說,實際上Body已經被執行過一次了,所以正確的做法是doStartTag()和doAfterBody()都要實現,doStartTag()實現第一次的處理,doAfterBody()實現後續的重複處理。

(2)<f:forEach>標籤處理器的實現中,必須先爲第一次的Body執行做屬性設置,這樣返回EVAL_BODY_INCLUDE後第一次執行Body內容時,纔可以有var所設置的屬性名稱可以訪問。接着調用doAfterBody(),其中再爲第二次之後的Body處理做屬性設置,如果需要再執行一次Body,則返回EVAL_BODY_AGAIN,再次執行完Body後又會調用doAfterBody()方法。如果不想執行Body了,z則返回SKIP_PAGE,流程會來到doEndTag()的執行。

4、處理Body運行結果

(1)BodyTag接口繼承自IterationTag接口,BodyTagSupport繼承自TagSupport類,將doStartTag()的默認返回值改爲EVAL_BODY_BUFFERED,並針對BodyTag接口做了簡單的實現。在繼承BodyTagSupport類實現自定義標籤時,如果doStartTag()返回了EVAL_BODY_BUFFERED,則會調用setBodyContent()方法而後調用doInitBody()方法,接着再執行標籤Body。

(2)基本上,BodyTagSupport實現自定義標籤時, 並不需要去重新定義setBodyContent()與doInitBody()方法,只需要知道這兩個方法執行後,在doAfterBody()或doEndTag()方法中,就可以通過getBodyContent()取得一個BodyContent對象(Writer的子對象),這個對象中包括Body內容執行後的結果。

(3)如果要加工後的Body內容輸出用戶的瀏覽器,通常會在doEndTag()中使用pageContext的getOut()取得JspWriter對象,然後利用它來輸出內容至用戶的瀏覽器。如果一定要在doAfterBody()中取得JspWriter對象,則必須通過BodyContent的getEnclosingWriter()方法。

5、與父標籤溝通

(1)Tag實例會在部使用時放回標籤池,所以若標籤上一次執行過後有狀態存在,下次再從標籤池中取出時,必須考慮進行狀態重置的動作,這個動作放在doStartTag()中完成。

 

 

 

 

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