在實作 Tag
介面相關類別時,依不同的時機,要定義不同的 doXxxTag()
方法,並依需求傳回不同的值。 doXxxTag()
方法實際上是分別定義在 Tag
與 IterationTag
介面上的方法,它們的繼承與實作架構如下所示:
類似 SimpleTag
介面,Tag
介面繼承自 JspTag
介面,它定義了基本的 Tag
行為,像是設定 PageContext
實例的 setPageContext()
、設定外層父標籤物件的 setParent()
方法、標籤物件銷毀前呼叫的 release()
方法等。
單是使用 Tag
介面的話,無法重複執行本體內容,而必須使用子介面 IterationTag
介面的 doAfterBody()
(之後會看到如何重複執行本體內容)。TagSupport
類別實作了 IteratorTag
介面,對介面上所有方法作了基本實作,所以只需要在繼承 TagSupport
之後,針對必要的方法重新定義即可。
當 JSP 中遇到 TagSupport
自訂標籤時,會進行以下的動作:
- 嘗試從標籤池(Tag Pool)找到可用的標籤物件,如果找到就直接使用,如果沒找到就建立新的標籤物件。
- 呼叫標籤處理器的
setPageContext()
方法設定PageContext
實例。 - 如果是巢狀標籤中的內層標籤,則還會呼叫標籤處理器的
setParent()
方法,並傳入外層標籤處理器的實例。 - 設定標籤處理器屬性(例如這邊是呼叫
IfTag
的setTest()
方法來設定)。 - 呼叫標籤處理器的
doStartTag()
方法,並依不同的傳回值決定是否執行本體或呼叫doAfterBody()
、doEndTag()
方法(之後詳述)。 - 將標籤處理器實例置入標籤池中以便再度使用。
首先注意到第 1 點與第 6 點,沒錯!Tag
實例是可以重複使用的(SimpleTag
實例則是每次請求都建立新物件,用完就銷毀回收),所以自訂 Tag
類別時,要注意物件狀態是否會保留下來,必要的時候,在 doStartTag()
方法中,可以進行狀態重置的動作。別以為可以使用 release()
方法來作狀態重置,因為 release()
方法只會在標籤實例真正被銷毀回收前被呼叫。
接著來詳細說明第 5 點。JSP 頁面會根據標籤處理器各方法呼叫的不同傳回值,來決定要呼叫哪一個方法或進行哪一個動作,這個直接使用流程圖來說明會比較清楚:
doStartTag()
可以回傳 EVAL_BODY_INCLUDE
或 SKIP_BODY
。如果傳回 EVAL_BODY_INCLUDE
則會執行本體內容,而後呼叫 doAfterBody()
(就相當於 SimpleTag
的 doTag()
中呼叫了 JspFragment
的 invoke()
方法)。如果不想執行本體內容,則可傳回 SKIP_BODY
(就相當於 SimpleTag
的 doTag()
不呼叫 JspFragment
的 invoke()
方法),此時就會呼叫 doEndTag()
方法。
這邊暫時不討論 doAfterBody()
方法的傳回值,因為 doAfterBody()
預設傳回值是 SKIP_BODY
,如果不重新定義 doAfterBody()
方法,無論有無執行本體,流程最後都會來到 doEndTag()
。在 doEndTag()
中,可傳回 EVAL_PAGE
或 SKIP_PAGE
。如果傳回 EVAL_PAGE
,則自訂標籤後續的 JSP 頁面才會繼續執行,如果傳回 SKIP_PAGE
就不會執行後續的 JSP 頁面(相當於 SimpleTag
的 doTag()
中丟出 SkipPageException
的作用)。
實際上,由於 TagSupport
類別對 IterationTag
介面作了基本實作,doStartTag()
、doAfterBody()
與 doEndTag()
都有預設的傳回值,依序分別是 SKIP_BODY
、SKIP_BODY
及 EVAL_PAGE
,也就是預設不處理本體,標籤處理結束後 會執行後續的JSP頁面。
實際上在 Tomcat 中,如果觀看 JSP 轉譯後的 Servlet 原始碼會發現,只要 doStartTag()
的傳回值不是 SKIP_BODY
,就會執行本體內容並呼叫 doAfterBody()
方法。doEndTag()
只要傳回值不是 SKIP_PAGE
,就會執行後續的 JSP 頁面。