了解生命週期與架構


在實作 Tag 介面相關類別時,依不同的時機,要定義不同的 doXxxTag() 方法,並依需求傳回不同的值。 doXxxTag() 方法實際上是分別定義在 TagIterationTag 介面上的方法,它們的繼承與實作架構如下所示:

了解生命週期與架構

類似 SimpleTag 介面,Tag 介面繼承自 JspTag 介面,它定義了基本的 Tag 行為,像是設定 PageContext 實例的 setPageContext()、設定外層父標籤物件的 setParent() 方法、標籤物件銷毀前呼叫的 release() 方法等。

單是使用 Tag 介面的話,無法重複執行本體內容,而必須使用子介面 IterationTag 介面的 doAfterBody()(之後會看到如何重複執行本體內容)。TagSupport 類別實作了 IteratorTag 介面,對介面上所有方法作了基本實作,所以只需要在繼承 TagSupport 之後,針對必要的方法重新定義即可。

當 JSP 中遇到 TagSupport 自訂標籤時,會進行以下的動作:

  1. 嘗試從標籤池(Tag Pool)找到可用的標籤物件,如果找到就直接使用,如果沒找到就建立新的標籤物件。
  2. 呼叫標籤處理器的 setPageContext() 方法設定 PageContext 實例。
  3. 如果是巢狀標籤中的內層標籤,則還會呼叫標籤處理器的 setParent() 方法,並傳入外層標籤處理器的實例。
  4. 設定標籤處理器屬性(例如這邊是呼叫 IfTagsetTest() 方法來設定)。
  5. 呼叫標籤處理器的 doStartTag() 方法,並依不同的傳回值決定是否執行本體或呼叫 doAfterBody()doEndTag() 方法(之後詳述)。
  6. 將標籤處理器實例置入標籤池中以便再度使用。

首先注意到第 1 點與第 6 點,沒錯!Tag 實例是可以重複使用的(SimpleTag 實例則是每次請求都建立新物件,用完就銷毀回收),所以自訂 Tag 類別時,要注意物件狀態是否會保留下來,必要的時候,在 doStartTag() 方法中,可以進行狀態重置的動作。別以為可以使用 release() 方法來作狀態重置,因為 release() 方法只會在標籤實例真正被銷毀回收前被呼叫。

接著來詳細說明第 5 點。JSP 頁面會根據標籤處理器各方法呼叫的不同傳回值,來決定要呼叫哪一個方法或進行哪一個動作,這個直接使用流程圖來說明會比較清楚:

了解生命週期與架構

doStartTag() 可以回傳 EVAL_BODY_INCLUDESKIP_BODY。如果傳回 EVAL_BODY_INCLUDE 則會執行本體內容,而後呼叫 doAfterBody()(就相當於 SimpleTagdoTag() 中呼叫了 JspFragmentinvoke() 方法)。如果不想執行本體內容,則可傳回 SKIP_BODY(就相當於 SimpleTagdoTag() 不呼叫 JspFragmentinvoke() 方法),此時就會呼叫 doEndTag() 方法。

這邊暫時不討論 doAfterBody() 方法的傳回值,因為 doAfterBody() 預設傳回值是 SKIP_BODY,如果不重新定義 doAfterBody() 方法,無論有無執行本體,流程最後都會來到 doEndTag()。在 doEndTag() 中,可傳回 EVAL_PAGESKIP_PAGE。如果傳回 EVAL_PAGE,則自訂標籤後續的 JSP 頁面才會繼續執行,如果傳回 SKIP_PAGE 就不會執行後續的 JSP 頁面(相當於 SimpleTagdoTag() 中丟出 SkipPageException 的作用)。

實際上,由於 TagSupport 類別對 IterationTag 介面作了基本實作,doStartTag()doAfterBody()doEndTag() 都有預設的傳回值,依序分別是 SKIP_BODYSKIP_BODYEVAL_PAGE,也就是預設不處理本體,標籤處理結束後 會執行後續的JSP頁面。

實際上在 Tomcat 中,如果觀看 JSP 轉譯後的 Servlet 原始碼會發現,只要 doStartTag() 的傳回值不是 SKIP_BODY,就會執行本體內容並呼叫 doAfterBody() 方法。doEndTag() 只要傳回值不是 SKIP_PAGE,就會執行後續的 JSP 頁面。