如果想繼承 TagSupport
實作〈處理標籤屬性與本體〉的 <f:forEach>
標籤,可以根據所給定的 Collection
物件個數來決定重複執行標籤本體的次數,那麼你該在哪個方法中實作? doStartTag()
?根據〈了解生命週期與架構〉中的流程圖,doStartTag()
只會執行一次!doEndTag()
?這時本體內容處理已經結束了!
根據〈了解生命週期與架構〉,在 doAfterBody()
方法執行過後,如果傳回 EVAL_BODY_AGAIN
, 則會再重複執行一次本體內容,而後再度呼叫 doAfterBody()
方法,除非在 doAfterBody()
中傳回 SKIP_BODY
才會呼叫 doEndTag()
。顯然地,doAfterBody()
是可以實作 <f:forEach>
標籤重複處理特性的地方。
不過這邊有點小陷阱!當 doStartTag()
傳回 EVAL_BODY_INCLUDE
後,會先執行本體內容後再呼叫 doAfterBody()
方法, 也就是說,實際上本體已經執行過一遍了!所以正確的作法應該是,doStartTag()
與 doAfterBody()
都要實作,doStartTag()
實作第一次的處理,doAfterBody()
實作後續的重複處理。例如:
package cc.openhome;
import java.util.Collection;
import java.util.Iterator;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.TagSupport;
public class ForEachTag extends TagSupport {
private String var;
private Iterator<Object> iterator;
@Override
public int doStartTag() throws JspException {
if(iterator.hasNext()) {
this.pageContext.setAttribute(var, iterator.next());
return EVAL_BODY_INCLUDE;
}
return SKIP_BODY;
}
@Override
public int doAfterBody() throws JspException {
if(iterator.hasNext()) {
this.pageContext.setAttribute(var, iterator.next());
return EVAL_BODY_AGAIN;
}
this.pageContext.removeAttribute(var);
return SKIP_BODY;
}
public void setVar(String var) {
this.var = var;
}
public void setItems(Collection<Object> items) {
this.iterator = items.iterator();
}
}
在 <f:forEach>
的標籤處理器實作中,必須先為第一次的本體執行作屬性設定,如此傳回 EVAL_BODY_INCLUDE
後第一次執行本體內容時,才可以有 var
所設定的 屬性名稱可以存取。接著呼叫 doAfterBody()
方法,其中再為第二次之後的本體處理作屬性設定,如果需要再執行一次本體,則傳回 EVAL_BODY_AGAIN
,再度執行完本體後又會呼叫 doAfterBody()
方法,如果不想執行本體了,則傳回 SKIP_PAGE
,則流程會來到 doEndTag()
的執行(在 SimpleTag
的 doTag()
中直接使用迴圈語法,顯然直覺多了)。
接著同樣在定義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>forEach</name>
<tag-class>cc.openhome.ForEachTag</tag-class>
<body-content>JSP</body-content>
<attribute>
<name>var</name>
<required>true</required>
<type>java.lang.String</type>
</attribute>
<attribute>
<name>items</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
<type>java.util.Collection</type>
</attribute>
</tag>
</taglib>
實際上在 Tomcat 中,如果觀看 JSP 轉譯後的 Servlet 原始碼會發現,只要 doAfterBody()
的傳回值不是 EVAL_BODY_AGAIN
,就不會再度執行本體內容並呼叫 doAfterBody()
方法。