如果要設計的自訂標籤是放置在某個標籤之中,而且必須與外層標籤作溝通,例如 JSTL 中的 <c:when>
、 <c:otherwise>
必須放在 <c:choose>
中,且 <c:when>
或 <c:otherwise>
必須得知先前的 <c:when>
是否已經測試通過並執行本體內容,如果是的話就不再執行測試。
了解生命週期與架構 中談過,當JSP中包括自訂標籤時,會建立自訂標籤處理器的實例,呼叫 setJspContext()
設定 PageContext
實例,再來若是巢狀標籤中的內層標籤,則還會呼叫標籤處理器的 setParent()
方法,並傳入外層標籤處理器的實例,這就是你與外層標籤接觸的機會。
接下來將以模彷 JSTL 的 <c:choose>
、<c:when>
、<c:otherwise>
標籤為例,製作自訂的 <f: choose>
、<f:when>
、<f:otherwise>
標籤,了解內層標籤如何與外層標籤溝通。首先來看看 <f:choose>
的標籤處理器如何撰寫:
package cc.openhome;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.SimpleTagSupport;
public class ChooseTag extends SimpleTagSupport {
private boolean matched;
@Override
public void doTag() throws JspException {
try {
this.getJspBody().invoke(null);
} catch (java.io.IOException ex) {
throw new JspException("ChooseTag 執行錯誤", ex);
}
}
public boolean isMatched() {
return matched;
}
public void setMatched(boolean matched) {
this.matched = matched;
}
}
ChooseTag
基本上沒什麼事,只是內含一個 boolean
型態的成員 matched
,預設是 false
。一旦內部的 <f:when>
有測試成功的情況,會 將 matched
設定為 true
。ChooseTag
的 doTag()
只需要作一件事,取得 JspFragment
並呼叫 invoke(null)
執行標籤本體內容。
再來看看 <f:when>
的標籤處理器實作:
package cc.openhome;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspTagException;
import javax.servlet.jsp.tagext.JspTag;
import javax.servlet.jsp.tagext.SimpleTagSupport;
public class WhenTag extends SimpleTagSupport {
private boolean test;
@Override
public void doTag() throws JspException {
JspTag parent = null;
if (!((parent = getParent()) instanceof ChooseTag)) {
throw new JspTagException("必須置於choose標籤中");
}
if (((ChooseTag) parent).isMatched()) {
return;
}
if (test) {
((ChooseTag) parent).setMatched(true);
try {
this.getJspBody().invoke(null);
} catch (java.io.IOException ex) {
throw new JspException("WhenTag 執行錯誤", ex);
}
}
}
public void setTest(boolean test) {
this.test = test;
}
}
<f:when>
可以設定 test
屬性來看看是否執行本體內容。在測試開始前,必須先嘗試取得 parent
,如果無法取得(也就是為 null
的情況),表示不在任何標籤之中;或是 parent
不為 ChooseTag
型態時,表示不是置於 <f:choose>
之中,這是個錯誤的使用方式,所以必須丟出例外。
如果確實是置於 <f:choose>
標籤之中,接著嘗試取得 parent
的 matched
狀態,如果已經被設定為 true
,表示先前有 <f:when>
已經通過測試並執行了其本體內容,那麼目前這個 <f:when>
就不用再作測試了。如果是置於 <f:choose>
之中,而且先前沒有 <f:when>
通過測試,接著就可以進行目前這個 <f:when>
的測試,如果測試成功,則設定 parent
的 matched
為 true
,並執行標籤本體。
接著來看 <f:otherwise>
的標籤處理器如何撰寫:
package cc.openhome;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspTagException;
import javax.servlet.jsp.tagext.JspTag;
import javax.servlet.jsp.tagext.SimpleTagSupport;
public class OtherwiseTag extends SimpleTagSupport {
@Override
public void doTag() throws JspException {
JspTag parent = null;
if (!((parent = getParent()) instanceof ChooseTag)) {
throw new JspTagException("WHEN_OUTSIDE_CHOOSE");
}
if (((ChooseTag) parent).isMatched()) {
return;
}
try {
this.getJspBody().invoke(null);
} catch (java.io.IOException ex) {
throw new JspException("Error in OtherwiseTag tag", ex);
}
}
}
<f:otherwise>
標籤的處理基本上與 <c:when>
類似,必須確認是否置於 <f:choose>
標籤之 中;必須確認先前是否有 <c:when>
測試成功,如果先前沒有 <c:when>
測試成功的話,就直接執行標籤本體內容。
接著記得定義 TLD 檔,在當中加入自訂標籤定義:
f.tld
<?xml version="1.0" encoding="UTF-8"?>
<taglib version="2.`" 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>choose</name>
<tag-class>cc.openhome.ChooseTag</tag-class>
<body-content>scriptless</body-content>
</tag>
<tag>
<name>when</name>
<tag-class>cc.openhome.WhenTag</tag-class>
<body-content>scriptless</body-content>
<attribute>
<name>test</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
<type>boolean</type>
</attribute>
</tag>
<tag>
<name>otherwise</name>
<tag-class>cc.openhome.OtherwiseTag</tag-class>
<body-content>scriptless</body-content>
</tag>
</taglib>
接下來使用自訂的 <f:choose>
、<f:when>
、<f:otherwise>
標籤改寫〈流程處理標籤〉中的例子:
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<%@taglib prefix="f" uri="https://openhome.cc/jstl/fake"%>
<jsp:useBean id="user" class="cc.openhome.User" />
<jsp:setProperty name="user" property="*" />
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>登入頁面</title>
</head>
<body>
<f:choose>
<f:when test="${user.valid}">
<h1>${user.name}登入成功</h1>
</f:when>
<f:otherwise>
<h1>登入失敗</h1>
</f:otherwise>
</f:choose>
</body>
</html>
你可以使用 getParent()
取得 parent
標籤,也就是目前標籤的上一層標籤。如果在一個數個巢狀的標籤中,想要直接取得某個指定類型的外層標籤,則可以透過 SimpleTagSupport
的 findAncestorWithClass()
靜態方法。例如:
SomeTag ancestor = (SomeTag) findAncestorWithClass(this, SomeTag.class);
findAncestorWithClass()
方法會在目前標籤的外層標籤中尋找,直到找到指定的類型之外層標籤物件後傳回。