處理本體執行結果


如果想要在本體執行過後,取得執行的結果並作適當處理該如何進行?例如實作一個〈處理標籤屬性與本體〉的 <f:toUpperCase> 標籤?只是繼承 TagSupport 的話沒辦法達到這個目的!你可以繼承 javax.servlet.jsp.tagext.BodyTagSupport 類別來實作,先來看看其類別架構:

處理本體執行結果

在上圖中多了 BodyTag 介面,其繼承自 IterationTag 介面,新增了 setBodyContent()doInitBody() 兩個方法,而 BodyTagSupport 則繼承自 TagSupport 類別,將 doStartTag() 的預設傳回值改為 EVAL_BODY_BUFFERED,並針對 BodyTag 介面作了簡單的實作。

在繼承 BodyTagSupport 類別實作自訂標籤時,如果 doStartTag() 傳回了 EVAL_BODY_BUFFERED,則會呼叫 setBodyContent() 方法而後呼叫 doInitBody() 方法,接著再執行標籤本體,也就是流程將變成以下:

處理本體執行結果

基本上,在使用 BodyTagSupport 實作自訂標籤時,並不需要去重新定義 setBodyContent()doInitBody() 方法,只需要知道這兩個方法執行過後,在 doAfterBody()doEndTag() 方法中,就可以透過 getBodyContent() 取得一個 BodyContent 物件(Writer 的子物件),這個物件中包括本體內容執行後的結果,例如透過 BodyContentgetString() 方法,就可以字串的方式傳回執行後的本體內容。

如果要將加工後的本體內容輸出使用者的瀏覽器,通常會在 doEndTag() 中使用 pageContextgetOut() 取得 JspWriter 物件,然後利用它來輸出內容至使用者的瀏覽器。如果在 doAfterBody() 中使用 pageContextgetOut() 方法所取得的物件,與 getBodyContent() 所取得的其實是相同的物件。如果在 doAfterBody() 中,要取得與 doEndTag() 中透過 pageContextgetOut() 取得的 JspWriter 物件,則必須透過 BodyContentgetEnclosingWriter() 方法。

原因可以在 JSP 轉譯後的 Servlet 程式碼中找到。如果 doStartTag() 傳回 EVAL_BODY_BUFFERED,則會使用 PageContextpushBody() 將目前的 JspWriter 置入堆疊中,並傳回一個 BodyContent 物件,而後呼叫 setBodyContent() 並傳入這個 BodyContent 物件,然後呼叫 doInitBody() 方法,而在呼叫 doEndTag() 方法前,如果先前 doStartTag() 傳回 EVAL_BODY_BUFFERED,則會呼叫 PageContextpopBody(),將原本的 JspWriter 從堆疊中取出。

以下使用 BodyTagSupport 類別來實作出〈處理標籤屬性與本體〉的 <f:toUpperCase> 標籤處理器作為示範:

package cc.openhome;

import java.io.IOException;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.BodyTagSupport;

public class ToUpperCaseTag extends BodyTagSupport {
    @Override
    public int doEndTag() throws JspException {
        String upper = this.getBodyContent().getString().toUpperCase();
        try {
            pageContext.getOut().write(upper);
        } catch (IOException ex) {
            throw new JspException(ex);
        }
        return EVAL_PAGE;
    }
} 

在這邊於 doEndTag() 中透過 getBodyContent() 取得 BodyContent 物件,並呼叫其 getString() 取得執行過後的標籤本體內容,再進行轉字母為大寫的動作。轉換後的本體內容,則透過 pageContextgetOut() 取得 JspWriter 進行輸出。

記得在 TLD 檔案中定義標籤:

f.tld

<?xml version="1.0" encoding="UTF-8"?>
<taglib version="2.1" xmlns="http://java.sun.com/xml/ns/j2ee"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
   web-jsptaglibrary_2_1.xsd">
    <tlib-version>1.0</tlib-version>
    <short-name>f</short-name>
    <uri>https://openhome.cc/jstl/fake</uri>
    // 略...
    <tag>
        <name>toUpperCase</name>
        <tag-class>cc.openhome.ToUpperCaseTag</tag-class>
        <body-content>JSP</body-content>
    </tag>
</taglib> 

接著就如同〈處理標籤屬性與本體〉的示範,可以如下使用這個標籤:

<f:toUpperCase>
    <f:forEach var="name" items="${names}">
        ${name} <br>
    </f:forEach>
</f:toUpperCase>