學習如何使用簡單標記 API 和構建用于求解 JSP 表達式的定制標記,如何控制 JSP 頁面中的流以及如何創建 Java 集合。
JavaServer Pages (JSP) 和 JSP 標準標記庫 (JSTL) 為 Web 開發人員提供了許多有用的標記(也稱作操作)。此外,JSP 2.0 還提供兩個 API,即標準標記 API 和簡單標記 API,用于構建定制標記/操作。前一個 API 繼承自 JSP 1.x,并由于歷史原因而由 JSTL 使用。(由于 JSTL 1.0 的開發在 JSP 2.0 之前,因此新 API 不包含 JSTL 1.1。)此外,JSTL 也不使用 JSP 片段和動態屬性等 JSP 新特性。本文使用 JSP 2.0 的新 API 和特性構建定制標記擴展 JSTL。本文提供 API 概述并演示如何開發
- 導出變量的標記
- 條件標記
- 迭代標記
- 具有動態屬性的標記
- 協調標記
簡單標記 API 概述
在 JSP 頁面中使用定制標記時,應用服務器的 JSP 容器將 <prefix:customTag> ...</prefix:customTag> 轉換為調用稱為標記處理類的方法的 Java 代碼。因此,如果要開發定制標記,必須提供一個標記處理類,此類必須使用 JSP 1.x 標準標記 API 或 JSP 2.0 簡單標記 API。比較一下這兩個 API,就會發現新 API 更易于使用。簡單標記 API 只有一個接口 (javax.servlet.jsp.tagext.SimpleTag),它定義了處理定制標記的方法。通常從 JSP 容器從 JSP 頁面中自動生成的 Java Servlet 中調用這些方法。
javax.servlet.jsp.tagext.SimpleTagSupport 類實現了 SimpleTag 接口,因此當標記處理類擴展 SimpleTagSupport 時只須編寫 doTag() 方法即可。以下步驟介紹了如何開發一個簡單的標記處理類:
第 1 步:設計定制標記
首先,必須為標記選擇一個名稱并設置它的屬性。然后,創建一個標記庫描述符 (TLD) 文件(采用由 JSP 規范定義的 XML 格式),以告知 JSP 容器如何處理和驗證定制標記。文本提供了一個名為 util.tld 的示例 TLD 文件。
第 2 步:創建標記處理類
必須提供一個用于實現 SimpleTag 接口的 Java 類。最簡單的方法是擴展 SimpleTagSupport 或它的某個子類。本文中的 VarTagSupport、IfTag 和 WhileTag 類用于擴展 SimpleTagSupport。其他標記處理類示例擴展 VarTagSupport。
如果要使用未在 TLD 文件中指定的屬性,則標記處理類必須實現 javax.servlet.jsp.tagext.DynamicAttributes 接口(如“具有動態屬性的標記”部分中介紹的 MapTag 示例所示)。
第 3 步:初始化標記處理類實例
每個標記處理類都必須包含一個不帶參數的公共構造函數,用于放置初始化代碼。本文中的某些標記處理類(EvalTag、ListTag 和 MapTag)包含一個無參數的公共構造函數,它使用默認值初始化實例變量。其他類(IfTag、WhileTag 和 ItemTag)沒有構造函數。請注意,Java 編譯器在類不包含任何構造函數的情況下自動生成一個無參數的公共構造函數,該函數不執行任何操作。
第 4 步:提供屬性設置方法
JSP 頁面中的標記屬性值通過 setAttribute() 方法傳遞給標記處理類。例如,本文中的 <u:eval> 標記包含四個屬性:var、scope、expr 和 type。EvalTag 處理類實現 setExpr() 和 setType() 方法,并從 VarTagSupport 繼承 setVar() 和 setScope()。
動態屬性通過 DynamicAttributes 接口定義的 setDynamicAttribute() 方法傳遞。
第 5 步:實現 doTag() 方法
該方法用于實現定制標記的邏輯。doTag() 方法由 JSP 容器繼所有屬性設置方法之后調用。此處可以使用 getJspContext() 獲得一個 javax.servlet.jsp.JspContext 對象來訪問 JSP 環境。可以調用 getJspBody(),它返回 javax.servlet.jsp.tagext.JspFragment 的實例,該實例表示位于 <prefix:customTag> 和 </prefix:customTag> 之間的 JSP 主體。如果要開發協同工作的標記,如 <u:list> 和 <u:item>(本文的最后一部分將對其進行介紹),則還可以使用 getParent() 和 findAncestorWithClass() 方法。
第 6 步:測試定制標記
使用定制標記的 JSP 頁面必須使用 <%@taglib%> 指令導入該標記的標記庫。當定制標記出現在 JSP 頁面中時,JSP 容器將生成創建標記處理類實例、調用屬性設置方法和調用 doTag() 方法的代碼。因此,在使用定制標記的 JSP 頁面的執行過程中將調用標記處理類方法。
限制和變通方法
為簡化標記處理 API,JSP 2.0 采取了一個限制:如果定制標記的處理類是基于簡單標記 API 的,則頁面作者不得在 <prefix:customTag> 和 </prefix:customTag> 之間使用 JSP 1.x 聲明 (<%!...%>)、JSP 1.x 表達式 (<%=...%>) 和 scriptlet (<%...%>)。大多數情況下,您可以將 JSP 頁面中的 Java 代碼移動到標記處理類中,或在 JSP 2.0 表達式 (${...})(可以在定制標記的主體中使用)中使用 JSTL。請注意,JSP 2.0 允許您在基于標準標記 API 的定制標記主體中使用 scriptlet。然而,由于不使用腳本的 JSP 頁面更易于維護,因此最好避免在 Web 頁中使用 Java 代碼。
我的上一篇 Oracle 技術網 (OTN) 文章“使用 JSP 2.0 EL API”介紹了簡單標記 API 的另一個限制并提供了變通方法。JspContext 類未提供對 JSP 隱式對象(如application、session、request 和 response)的訪問。大多數應用服務器(包括 Oracle Application Server Containers for J2EE (OC4J) 10g)允許將 JSP 上下文轉換為 PageContext
標記處理類不適用于使用 println() 語句生成大量可重用的 HTML 代碼。JSP 2.0 為此工作提供了一個更好的方法。所謂的標記文件使用 JSP 語法并由 JSP 容器自動轉換為基于簡單標記 API 的標記處理類。我的另一篇 OTN 文章“創建 JSP 2.0 標記文件”介紹了這個 JSP 新特性。
導出變量的標記
許多 JSTL 標記實現某個邏輯并導出 JSP 變量以報告結果。例如,<sql:query> 包含一個 var 屬性,該屬性必須指定用于保存 SQL 結果集的 JSP 變量的名稱。var 屬性對其他 JSTL 標記(如 <fmt:formatNumber> 和 <fmt:formatDate>)來說是可選的。如果 var 屬性不存在,則這些標記將輸出它們的結果。所有包含 var 屬性的標記還包含一個 scope 屬性,該屬性可用于指示以下 JSP 變量的作用域:page、request、session 或 application。
VarTagSupport 類(它是為本文開發的一個示例)擴展 SimpleTagSupport 并為 var 和 scope 屬性提供設置方法。VarTagSupport 包含用于導出 JSP 變量、獲取主體內容和輸出內容的實用方法,而不是實現 doTag() 方法。這些方法由 VarTagSupport 的子類在 doTag() 中使用。本文包含四個用于擴展 VarTagSupport 的標記處理類(EvalTag、MapTag、ListTag 和 ItemTag)。
請注意,JSP 變量在 JSTL 規范中稱作范圍變量,而在 JSP 規范中稱作具名變量或范圍屬性。這些變量通過 JspContext 類的 setAttribute() 方法創建/導出。您可以在 JSP 頁面中使用 ${varName},以及在 Java 代碼中使用 JspContext 的 getAttribute() 或 findAttribute() 方法取得它們的值。不要混淆 JSP 變量與標記屬性。
實現屬性設置方法
JSP 容器調用屬性設置方法,將標記屬性的值傳遞給定制標記處理類。VarTagSupport 的 setVar() 方法將 var 屬性的值存儲在受保護的實例變量 (varName) 中。setScope() 方法將它的參數轉換為整數常數。如果該參數包含有效值(page、request、session 或 application),則將此整數常數存儲在另一受保護的實例變量 (varScope) 中。否則,setScope() 將拋出 JspException:
package jsputils.tags;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.tagext.SimpleTagSupport;
...
public class VarTagSupport extends SimpleTagSupport {
protected String varName;
protected int varScope;
protected VarTagSupport() {
varScope = PageContext.PAGE_SCOPE;
}
public void setVar(String name) throws JspException {
varName = name;
}
public void setScope(String scope) throws JspException {
if (scope.equalsIgnoreCase("page"))
varScope = PageContext.PAGE_SCOPE;
else if (scope.equalsIgnoreCase("request"))
varScope = PageContext.REQUEST_SCOPE;
else if (scope.equalsIgnoreCase("session"))
varScope = PageContext.SESSION_SCOPE;
else if (scope.equalsIgnoreCase("application"))
varScope = PageContext.APPLICATION_SCOPE;
else
throw new JspException("Invalid scope:" + scope);
}
...
}
將變量導出到 JSP 環境
如果 var 屬性存在,并具有非 null 值 (varName != null),則 export() 方法使用 getJspContext() 取得 JSP 上下文。隨后,如果 value 參數不為 null,則 export() 將使用 JSP 上下文的 setAttribute() 方法設置 JSP 變量。可以在 JSP 頁面中使用 ${varName} 取得變量值。如果 value 參數為 null,則 export() 將調用 removeAttribute(),后者從給定的范圍中刪除任何具有給定名稱的現有變量。
如果 var 屬性不存在或具有 null 值,則 export() 方法將返回 false。否則,export() 將返回 true:
package jsputils.tags;
import javax.servlet.jsp.JspContext;
import javax.servlet.jsp.tagext.SimpleTagSupport;
...
public class VarTagSupport extends SimpleTagSupport {
...
protected boolean export(Object value) {
if (varName == null)
return false;
JspContext jspContext = getJspContext();
if (value != null)
jspContext.setAttribute(varName, value, varScope);
else
jspContext.removeAttribute(varName, varScope);
return true;
}
...
}
取得由標記主體生成的內容
標記處理類可以使用從 SimpleTagSupport 繼承的 SimpleTagSupport 方法取得表示所處理 JSP 標記主體的 JspFragment。然后,標記處理類可以使用 invoke() 方法執行 JSP 片段;如果要捕獲由 JSP 主體生成的內容,則該方法需要 java.io.Writer 參數。invokeBody() 方法將這些操作分組,并返回 String 類型的主體內容:
package jsputils.tags;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.JspFragment;
import javax.servlet.jsp.tagext.SimpleTagSupport;
...
import java.io.StringWriter;
import java.io.IOException;
public class VarTagSupport extends SimpleTagSupport {
...
protected String invokeBody() throws JspException {
JspFragment body = getJspBody();
StringWriter buffer = new StringWriter();
try {
body.invoke(buffer);
} catch (IOException x) {
throw new JspException(x);
}
return buffer.toString();
}
...
}
請注意,如果只想輸出由 JSP 主體生成的內容,則可以使用 null 參數調用 invoke() 方法。如果不調用 invoke(),則不執行定制標記的 JSP 主體。
在標記執行過程中生成內容
標記處理類可以使用由 JSP 上下文的 getOut() 方法返回的 JspWriter 輸出內容:
package jsputils.tags;
import javax.servlet.jsp.JspContext;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspWriter;
import javax.servlet.jsp.tagext.SimpleTagSupport;
...
import java.io.IOException;
public class VarTagSupport extends SimpleTagSupport {
...
protected void write(String str) throws JspException {
JspContext jspContext = getJspContext();
JspWriter out = jspContext.getOut();
try {
out.write(str);
} catch (IOException x) {
throw new JspException(x);
}
}
}
開發導出變量的定制標記
“使用 JSP 2.0 EL API”介紹了名為 ELUtils 的類的開發,該類的方法在 Java 代碼中求解 JSP 表達式。當您要在 JSP 頁面外使用 EL時(如在 XML 文件中使用),EL API 將很有幫助。在“使用 JSP 2.0 EL API”中,我們將 ELUtils 的靜態方法映射為 EL 函數。這次,我們將構建一個定制標記 (<u:eval>),它調用 ELUtils 的某個 evaluate() 方法。<u:eval> 標記由名為 EvalTag 的類處理,該類為 <u:eval> 的屬性實現兩個設置方法(setExpr() 和 setType())。屬性 expr 和 type 的值被傳遞給 evaluate() 方法,該方法返回表達式的值。
如果 expr 屬性不存在,則 doTag() 方法將調用從 VarTagSupport 類繼承的 invokeBody() 方法以取得主體內容(應為表達式)。因此,調用 <u:eval> 標記的 JSP 頁面可以將表達式指定為 expr 屬性的值或置于 <u:eval> 和 </u:eval>之間。
EvalTag 類擴展了 VarTagSupport,這是因為它需要 export(),以便使用 var 屬性指定的名稱和由 evaluate() 返回的值創建 JSP 變量。如果 var 屬性不存在,則 export() 無法設置變量并返回 false。這種情況下,EvalTag 使用從 VarTagSupport 繼承的 write() 方法輸出所求解表達式的值。
EvalTag 處理類的源代碼如下所示:
package jsputils.tags;
import jsputils.el.ELUtils;
import javax.servlet.jsp.JspException;
public class EvalTag extends VarTagSupport {
private String strExpr;
private Object varType;
public EvalTag() {
varType = Object.class;
}
public void setExpr(String expr) throws JspException {
strExpr = expr;
}
public void setType(Object type) throws JspException {
varType = type;
}
protected Object evaluate(String expression,
Object expectedType) throws JspException {
return ELUtils.evaluate(
expression, expectedType, getJspContext());
}
public void doTag() throws JspException {
if (strExpr == null)
strExpr = invokeBody();
Object value = evaluate(strExpr, varType);
boolean exported = export(value);
if (!exported && value != null)
write(value.toString());
}
}
在庫描述符中定義定制標記
<u:eval> 標記定義在名為 util.tld 的 XML 文件中。JSP 容器使用此文件將定制標記映射為它的處理類 (EvalTag)。除標記的名稱和標記處理類外,該描述符還包含有關標記主體和屬性的信息。
主體內容被聲明為 scriptless,這意味著不能在 <u:eval> 和 </u:eval> 之間使用 Java 代碼 (scriptlet)。如果標記不使用它的主體內容,則應指定 empty 而非 scriptless。請注意,對于標準標記,還可以指定 JSP,它允許在標記的主體中使用 Java scriptlet。基于簡單標記 API 開發處理類時,必須將所有 Java 代碼置于 Java 類中。
util.tld 描述符為 <u:eval> 標記定義了四個屬性:expr、type、var 和 scope。所有屬性都被聲明為可選(required 為 false)。expr 和 type 的值可以包含 JSP 表達式(rtexprvalue 為 true),但 var 和 scope 屬性必須具有固定值(rtexprvalue 為 false)。
定制標記在 <taglib> 元素中描述,該元素包含版本號、短名稱(前綴)和統一資源標識符 (URI)(不一定指示現有 Web 資源):
<?xml version="1.0" encoding="UTF-8" ?>
<taglib 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_0.xsd"
version="2.0">
<tlib-version>1.0</tlib-version>
<short-name>u</short-name>
<uri>http://otn.oracle.com/jsp/taglib/util.tld</uri>
<tag>
<name>eval</name>
<tag-class>jsputils.tags.EvalTag</tag-class>
<body-content>scriptless</body-content>
<attribute>
<name>expr</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>type</name>
<required>false</required>
<rtexprvalue>false</rtexprvalue>
</attribute>
<attribute>
<name>var</name>
<required>false</required>
<rtexprvalue>false</rtexprvalue>
</attribute>
<attribute>
<name>scope</name>
<required>false</required>
<rtexprvalue>false</rtexprvalue>
</attribute>
</tag>
...
</taglib>
在 JSP 頁面中使用定制標記
示例 Web 應用程序的 web.xml 描述符定義了兩個參數:debug_mode 和 tags_db_dataSource。debug_mode 參數指示應用程序是運行在測試環境中還是運行在生產環境中。tags_db_dataSource 參數使用 EL 根據 debug_mode 的值選擇數據源名稱:
<?xml version="1.0" encoding="ISO-8859-1"?>
<web-app 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-app_2_4.xsd"
version="2.4">
<context-param>
<param-name>debug_mode</param-name>
<param-value>true</param-value>
</context-param>
<context-param>
<param-name>tags_db_dataSource</param-name>
<param-value>jdbc/${
initParam.debug_mode ?"dbtags" :"production"
}</param-value>
</context-param>
</web-app>
使用 <%@taglib%> 導入本文的標記庫后,EvalTest.jsp 頁面將使用 <u:eval> 標記求解 web.xml 文件中的表達式:
<!-- EvalTest.jsp -->
<%@ taglib prefix="u" uri="/WEB-INF/util.tld" %>
<u:eval expr="${initParam.tags_db_dataSource}" var="db"/>
${db}
<u:eval expr="${initParam.tags_db_dataSource}"/>
<u:eval>${initParam.tags_db_dataSource}</u:eval>
該 JSP 頁面測試兩種指定表達式的方法:使用 expr 屬性以及置于 <u:eval> 和 </u:eval> 之間。var 屬性用于創建名為 db 的 JSP 變量,它的值使用 ${db} 輸出。如果 var 屬性不存在,則 <u:eval> 標記輸出所求解表達式的值。以下是 EvalTest.jsp 生成的輸出:
jdbc/dbtags jdbc/dbtags jdbc/dbtags
條件標記
JSTL 提供了幾個條件標記(<c:if>、<c:choose>、<c:when> 和 <c:otherwise>)以及一個用于捕獲 JSP 頁面中異常的標記 (<c:catch>)。這些標記雖然簡單、有用,但并非得益于 JSP 2.0 的片段屬性特性,該特性允許單個標記處理多個 JSP 片段。本文的此部分使用片段屬性構建一個更復雜的名為 <u:if> 并由 IfTag 類處理的條件標記。IfTag 示例還演示了如何捕獲在 JSP 片段執行過程中可能發生的任何異常。
使用片段屬性
假設有一個包含兩個文本域(unitPrice 和 quantity)的表單,需要計算總價。還需要處理用戶未填寫表單或提供非數字值(可能生成 NumberFormatException)的情況。在實際應用程序中,可能會使用框架(如 JavaServer Faces (JSF))生成 HTML 表單和驗證用戶輸入。但為了測試本部分中開發的條件標記,假設要創建不使用專用標記庫的表單。以下是要使用的代碼:
<!-- IfTest.jsp -->
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="u" uri="/WEB-INF/util.tld" %>
<html>
<body>
<form method="post">
<c:set var="paramsProvided"
value="${!empty param.unitPrice and !empty param.quantity}"/>
...
<p> Unit Price:
<input type="text" name="unitPrice" size="10"
value="<c:out value='${param.unitPrice}'/>">
<p> Quantity:
<input type="text" name="quantity" size="10"
value="<c:out value='${param.quantity}'/>">
<p> <input type="submit" value="Calculate Price">
</form>
</body>
</html>
以下代碼演示了如何使用 JSTL 的 <c:if> 和 <c:catch> 標記驗證表單數據:
<c:if test="${paramsProvided}">
<c:catch var="error">
<c:set var="price"
value="${param.unitPrice * param.quantity}"/>
<p> Price:${price}
</c:catch>
</c:if>
<c:if test="${not paramsProvided}">
<p> Please fill out the form
</c:if>
<c:if test="${error != null}">
<p> Number format error
</c:if>
前面的代碼段不是 IfTest.jsp 的一部分。該頁面使用定制標記 <u:if>(包含 test 屬性,如 <c:if>)而不是使用 JSTL 驗證表單數據。<u:if> 標記包含三個條件屬性,即 TRUE、FALSE 和 ERROR。這些屬性的值是由 <jsp:attribute> 標準操作包含的 JSP 片段:
<u:if test="${paramsProvided}">
<jsp:attribute name="TRUE">
<c:set var="price"
value="${param.unitPrice * param.quantity}"/>
<p> Price:${price}
</jsp:attribute>
<jsp:attribute name="FALSE">
<p> Please fill out the form
</jsp:attribute>
<jsp:attribute name="ERROR">
<p> Number format error
</jsp:attribute>
</u:if>
聲明片段屬性
必須使用 <fragment>true</fragment> 在描述符文件中聲明片段屬性:
<taglib ...>
...
<tag>
<name>if</name>
<tag-class>jsputils.tags.IfTag</tag-class>
<body-content>empty</body-content>
<attribute>
<name>test</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>TRUE</name>
<required>true</required>
<fragment>true</fragment>
</attribute>
<attribute>
<name>FALSE</name>
<required>false</required>
<fragment>true</fragment>
</attribute>
<attribute>
<name>ERROR</name>
<required>false</required>
<fragment>true</fragment>
</attribute>
</tag>
...
</taglib>
處理片段屬性
IfTag 處理類具有 <u:if> 的常規 test 屬性以及 TRUE、FALSE 和 ERROR 片段屬性的設置方法。doTag() 方法使用 JspFragment 類的 invoke() 方法執行 JSP 片段:
package jsputils.tags;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.JspFragment;
import javax.servlet.jsp.tagext.SimpleTagSupport;
import java.io.IOException;
public class IfTag extends SimpleTagSupport {
private boolean testAttr;
private JspFragment trueAttr;
private JspFragment falseAttr;
private JspFragment errorAttr;
public void setTest(boolean test) {
testAttr = test;
}
public void setTRUE(JspFragment fragment) {
trueAttr = fragment;
}
public void setFALSE(JspFragment fragment) {
falseAttr = fragment;
}
public void setERROR(JspFragment fragment) {
errorAttr = fragment;
}
public void doTag() throws JspException, IOException {
try {
if (testAttr) {
if (trueAttr != null)
trueAttr.invoke(null);
} else {
if (falseAttr != null)
falseAttr.invoke(null);
}
} catch (Exception x) {
if (errorAttr != null)
errorAttr.invoke(null);
else
throw new JspException(x);
}
}
}
迭代標記
JSTL 具有三個迭代標記(<c:forEach>、<c:forTokens> 和 <x:forEach>)。本部分演示了如何實現在我的上篇文章中介紹的 <u:while> 標記(在每次迭代前使用 EL API 判斷其條件)。
在定制標記中使用 JSTL 函數庫
JSTL 1.1 提供了一個函數庫來實現許多有用的字符串操作。以下代碼嘗試使用 JSTL 的 fn:split() 函數將字符串 (a1/a2//b//c1/c2/c3) 拆分為三個標記(a1/a2、b 和 c1/c2/c3):
<!-- SplitTest.jsp -->
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<p>fn:split -
<c:set var="str" value="a1/a2//b//c1/c2/c3"/>
<c:set var="delim" value="http://"/>
<c:set var="array" value="${fn:split(str, delim)}"/>
<c:forEach var="token" items="${array}">
[<c:out value="${token}"/>]
</c:forEach>
上面的代碼未生成所要的結果,這是因為 fn:split() 是基于 java.util.StringTokenizer 的,后者將 delim 參數作為一組分隔字符處理。以下是 SplitTest.jsp 生成的輸出:
fn:split - [a1] [a2] [b] [c1] [c2] [c3]
JSTL 提供了其他字符串函數(如 fn:contains()、fn:substringBefore() 和 fn:substringAfter()),可以通過循環使用這些函數將 a1/a2//b//c1/c2/c3 拆分為 a1/a2、b 和 c1/c2/c3。該循環由 <u:while> 標記控制:
<!-- WhileTest.jsp -->
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<%@ taglib prefix="u" uri="/WEB-INF/util.tld" %>
<p>u:while -
<c:set var="str" value="a1/a2//b//c1/c2/c3"/>
<c:set var="delim" value="http://"/>
<u:while test="\${fn:contains(str, delim)}">
[<c:out value="${fn:substringBefore(str, delim)}"/>]
<c:set var="str" value="${fn:substringAfter(str, delim)}"/>
</u:while>
[<c:out value="${str}"/>]
請注意,$ 字符在 test 條件中使用反斜杠進行了轉義。因此,JSP 容器將 test 屬性的值作為文本處理,而不求解 JSP 表達式的值。<u:while> 標記由 WhileTag 類處理,該類在每此迭代前求解 ${fn:contains(str, delim)} 表達式。${fn:substringBefore(str, delim)} 表達式返回字符串的第一個標記,而 ${fn:substringAfter(str, delim)} 返回剩余標記。WhileTest.jsp 頁面生成所要的輸出:
u:while - [a1/a2] [b] [c1/c2/c3]
多次求解同一 JSP 表達式
標記處理類可以使用 invoke() 方法多次調用定制標記的主體。還可以使用同一 invoke() 方法多次執行片段屬性。但如果要對常規屬性重新求值,則必須使用 EL API?!笆褂?JSP 2.0 EL API”中的 PEWrapper 類使您能夠通過一次性分析 JSP 表達式來優化此過程。請注意,此示例類只是標準 EL API 的包裝類。WhileTag 處理類的 doTag() 方法創建一個 PEWrapper 實例,該實例需要必須求解的表達式 (strTest)、預期的類型 (Boolean)、JSP 上下文和一個函數映射類(使您能夠在 JSP 表達式中使用 JSTL 函數)。FNMapper 類(“使用 JSP 2.0 EL API”中提供了該類)將 JSTL 函數映射為一組由 JSTL 的 Apache 實現提供的靜態 Java 方法。當 test 表達式為 true 時,doTag() 方法執行 <u:while> 標記的 JSP 主體:
package jsputils.tags;
import jsputils.el.PEWrapper;
import jsputils.el.FNMapper;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.SimpleTagSupport;
import java.io.IOException;
public class WhileTag extends SimpleTagSupport {
private String strTest;
public void setTest(String test) throws JspException {
strTest = test;
}
public void doTag() throws JspException, IOException {
PEWrapper parsedExpr = PEWrapper.getInstance(
strTest, Boolean.class, getJspContext(),
FNMapper.getInstance("fn"));
while (((Boolean) parsedExpr.evaluate()).booleanValue())
getJspBody().invoke(null);
}
}
util.tld 文件中定義了 <u:while> 和 WhileTag 類之間的映射:
<taglib ...>
...
<tag>
<name>while</name>
<tag-class>jsputils.tags.WhileTag</tag-class>
<body-content>scriptless</body-content>
<attribute>
<name>test</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
...
</taglib>
具有動態屬性的標記
JSTL 提供對 Java 集合的支持。例如,可以使用 <c:set> 為 java.util.Map 添加元素。但無法使用 JSTL 創建 Map 對象。本部分構建了一個定制標記,該標記創建一個用于保存 java.util.LinkedHashMap 的 JSP 變量。您可以按以下示例所示在 JSP 頁面中創建 Map 對象:
<u:map var="langMap" scope="application"
en="English" de="Deutsch" fr="Fraais"
it="Italiano" es="Espol"/>
此 Map 對象使用標記的動態屬性(en、de、fr、it 和 es)進行初始化。這些屬性之所以稱作動態屬性是因為它們不在庫描述符中聲明。
處理動態屬性
定制標記可以通過實現 DynamicAttributes 接口(它只有一個方法 setDynamicAttribute())處理動態屬性。JSP 容器對未在 TLD 文件中聲明的每個屬性調用此方法。
MapTag 處理類擴展了 VarTagSupport 并實現了 DynamicAttributes。LinkedHashMap 實例在 MapTag() 構造函數中創建。setDynamicAttribute() 方法通過將屬性名稱作為屬性值的鍵為 Map 對象中添加元素。doTag() 方法使用從 VarTagSupport 繼承的 export() 方法將 Map 對象導出為 JSP 變量:
package jsputils.tags;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.DynamicAttributes;
import java.util.LinkedHashMap;
public class MapTag extends VarTagSupport
implements DynamicAttributes {
private LinkedHashMap map;
public MapTag() {
map = new LinkedHashMap();
}
public void setDynamicAttribute(String uri,
String localName, Object value)
throws JspException {
map.put(localName, value);
}
public void doTag() throws JspException {
export(map);
}
}
請注意,LinkedHashMap 保留其元素的順序,這意味著由 iterator() 方法返回的 java.util.Iterator 按元素添加到 Map 對象的順序訪問這些元素。
必須在 util.tld 文件中聲明對動態屬性以及 <u:map> 標記的 var 和 scope 屬性的支持:
<taglib ...>
...
<tag>
<name>map</name>
<tag-class>jsputils.tags.MapTag</tag-class>
<body-content>empty</body-content>
<attribute>
<name>var</name>
<required>true</required>
<rtexprvalue>false</rtexprvalue>
</attribute>
<attribute>
<name>scope</name>
<required>false</required>
<rtexprvalue>false</rtexprvalue>
</attribute>
<dynamic-attributes>true</dynamic-attributes>
</tag>
...
</taglib>
在 JSP 頁面中使用 Map 對象
MapTest.jsp 頁面使用 <u:map> 標記創建兩個包含某個虛擬網站所支持的語言和版本的 Map 對象(langMap 和 versionMap)。然后,該頁面使用 JSTL 的 <c:redirect> 標記將用戶重定向到 MapTest2.jsp:
<!-- MapTest.jsp -->
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="u" uri="/WEB-INF/util.tld" %>
<u:map var="langMap" scope="application"
en="English" de="Deutsch" fr="Fraais"
it="Italiano" es="Espol"/>
<c:set var="defaultLang" scope="application" value="en"/>
<u:map var="versionMap" scope="application"
html="HTML" java="Java" flash="Flash"/>
<c:set var="defaultVersion" scope="application" value="html"/>
<c:redirect url="MapTest2.jsp"/>
如果 session 范圍中尚未存在此 JSP 變量,則 MapTest2.jsp 頁面將再創建一個 Map 對象 (prefMap)。這三個 Map 對象(包含語言、版本和用戶首選項)用于創建一個簡單的 HTML 表單,允許用戶更改他們的首選語言和站點版本:
<!-- MapTest2.jsp -->
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="u" uri="/WEB-INF/util.tld" %>
<c:if test="${prefMap == null}">
<u:map var="prefMap" scope="session"
lang="${defaultLang}" version="${defaultVersion}"/>
</c:if>
<form method="post" action="MapTest3.jsp">
<p>Language:<br>
<select name="lang" size="1">
<c:forEach var="lang" items="${langMap}">
<c:set var="selected" value=""/>
<c:if test="${lang.key == prefMap.lang}">
<c:set var="selected" value="selected"/>
</c:if>
<option value="${lang.key}" ${selected}>
${lang.value}
</option>
</c:forEach>
</select>
<p>Version:<br>
<c:forEach var="version" items="${versionMap}">
<c:set var="checked" value=""/>
<c:if test="${version.key == prefMap.version}">
<c:set var="checked" value="checked"/>
</c:if>
<input type="radio" name="version" ${checked}
value="${version.key}"> ${version.value} <br>
</c:forEach>
<p> <input type="submit" value="Save">
</form>
以下是由 MapTest2.jsp 生成的 HTML 表單:
<form method="post" action="MapTest3.jsp">
<p>Language:<br>
<select name="lang" size="1">
<option value="en" selected>
English
</option>
<option value="de" >
Deutsch
</option>
<option value="fr" >
Fraais
</option>
<option value="it" >
Italiano
</option>
<option value="es" >
Espol
</option>
</select>
<p>Version:<br>
<input type="radio" name="version" checked
value="html"> HTML <br>
<input type="radio" name="version"
value="java"> Java <br>
<input type="radio" name="version"
value="flash"> Flash <br>
<p> <input type="submit" value="Save">
</form>
MapTest3.jsp 將用戶選擇的首選項保存到 prefMap 變量中并顯示用戶首選項:
<!-- MapTest3.jsp -->
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="u" uri="/WEB-INF/util.tld" %>
<u:map var="prefMap" scope="session"
lang="${param.lang}" version="${param.version}"/>
<p>User Preferences:
<ul>
<li>Language:${prefMap.lang}</li>
<li>Version:${prefMap.version}</li>
</ul>
<form method="post" action="MapTest2.jsp">
<p> <input type="submit" value="Change">
</form>
協調標記
JSTL 的 <sql:param> 標記與 <sql:query> 和 <sql:update> 配合使用,來傳遞所執行 SQL 語句的參數。本文使用了同一技巧來實現兩個定制標記(<u:list> 和 <u:item>),它們協同在 JSP 頁面中創建一個 java.util.ArrayList。<u:list> 標記可以是一個或多個包含此列表元素的 <u:item> 標記的父標記,如下例所示:
<u:list var="services">
<u:item> E-Mail </u:item>
<u:item> Web Hosting </u:item>
<u:item> E-Commerce </u:item>
</u:list>
<u:item> 標記不需要直接包含在 <u:list> 中。您可以在 <u:list> 和 <u:item> 之間放置其他標記,如 <c:forEach>。這使您能夠在循環中添加元素。如前一個代碼片段所示,可以在 <u:item> 和 </u:item> 之間指定這些元素的值,也可以使用 <u:item> 的 value 屬性指定:
<u:list var="selectedServices">
<c:forEach var="index" items="${paramValues.selected}">
<c:set var="paramName" value="service${index}"/>
<u:item value="${param[paramName]}"/>
</c:forEach>
</u:list>
這些代碼示例是 ListTest.jsp 和 ListTest2.jsp 頁面的一部分,位于 ListTag 和 ItemTag 類之后。
實現協同工作的標記處理類
ListTag 處理類在其構造函數中創建 ArrayList 實例,并提供一個公共 add() 方法為列表添加元素。doTag() 方法調用所處理的 <u:list> 標記的主體并將列表導出為 JSP 變量:
package jsputils.tags;
import javax.servlet.jsp.JspException;
import java.util.ArrayList;
import java.io.IOException;
public class ListTag extends VarTagSupport {
private ArrayList list;
public ListTag() {
list = new ArrayList();
}
public void add(Object item) {
list.add(item);
}
public void doTag() throws JspException, IOException {
getJspBody().invoke(null);
export(list);
}
}
<u:item> 標記由 ItemTag 類處理,后者的 doTag() 方法使用 findAncestorWithClass() 定位 ListTag 實例 — 該實例處理最近的 <u:list> 祖先標記。該列表元素被傳遞給祖先標記的處理類的 add() 方法:
package jsputils.tags;
import javax.servlet.jsp.JspException;
public class ItemTag extends VarTagSupport {
private Object itemValue;
public void setValue(Object value) throws JspException {
itemValue = value;
}
public void doTag() throws JspException {
ListTag ancestor = (ListTag) findAncestorWithClass(
this, ListTag.class);
if (ancestor == null)
throw new JspException(
"Couldn't find 'list' ancestor for 'item'");
if (itemValue == null)
itemValue = invokeBody();
ancestor.add(itemValue);
}
}
同其他常規標記一樣,這兩個標記在 TLD 文件中聲明:
<taglib ...>
...
<tag>
<name>list</name>
<tag-class>jsputils.tags.ListTag</tag-class>
<body-content>scriptless</body-content>
<attribute>
<name>var</name>
<required>true</required>
<rtexprvalue>false</rtexprvalue>
</attribute>
<attribute>
<name>scope</name>
<required>false</required>
<rtexprvalue>false</rtexprvalue>
</attribute>
</tag>
<tag>
<name>item</name>
<tag-class>jsputils.tags.ItemTag</tag-class>
<body-content>scriptless</body-content>
<attribute>
<name>value</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
</taglib>
在 JSP 頁面中使用 List 對象
ListTest.jsp 頁面創建一個包含服務的列表,并使用它生成一個 HTML 表單,該表單允許用戶選擇一個或多個服務:
資源
使用以下資源測試這些示例并了解有關 JSP 2.0 簡單標記 API 的更多信息。
下載源代碼。jsptags_src.zip 文件包含本文的示例:Java 類均位于 jsputils 目錄中;jspelapi 是一個 Java Web 應用程序。要運行這些示例,需要 J2SE、J2EE 1.4 應用服務器和 JSTL 1.1。
閱讀“創建 JSP 2.0 標記文件”。Andrei Cioroianu 展示了如何創建和使用標記文件,以及如何將現有頁面片段變換為標記文件。他使用 JSTL 和幾個高級 JSP 特性構建用于更新和查詢數據庫的標記文件。
閱讀《使用 JSP 2.0 EL API》。Andrei Cioroianu 演示了如何動態求解 JSP 表達式、如何在 XML 配置文件中使用表達式語言以及如何在顯示 SQL 結果集時優化 EL 的使用。
下載 OC4J 10g。OC4J 10g 開發人員預覽版 2 完全采用 J2EE 1.4 規范(包括 JSP 2.0)??梢允褂?OC4J 10g (10.0.3) 來測試這些示例。
下載 JSTL 1.1。部署 jsptags Web 應用程序之前,下載 JSTL 并將文件 jstl.jar 和 standard.jar 復制到 jsptags/WEB-INF/lib 目錄中。
閱讀 JSP 2.0 規范。JSP 2.0 規范有整個一章(“第 1 部分:第 JSP.7 章 - 標記擴展”)是專門為要構建定制標記庫的開發人員準備的。簡單標記 API 和標準標記 API 在另一章(“第 2 部分:第 JSP.13 章 - 標記擴展 API”)中進行了介紹。
JSP 示例和教程。JSP 示例代碼
教程:了解 JSP 2.0 的新特性 |
<!-- ListTest.jsp -->
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<%@ taglib prefix="u" uri="/WEB-INF/util.tld" %>
<u:list var="services">
<u:item> E-Mail </u:item>
<u:item> Web Hosting </u:item>
<u:item> E-Commerce </u:item>
</u:list>
<form method="post" action="ListTest2.jsp">
<p> Services:<br>
<c:forEach var="index" begin="${0}"
end="${fn:length(services)-1}">
<input type="checkbox" checked
name="selected" value="${index}">
<c:out value="${services[index]}"/>
<input type="hidden" name="service${index}"
value="${services[index]}"> <br>
</c:forEach>
<p> <input type="submit" value="Select">
</form>
以下是由 ListTest.jsp 生成的 HTML 表單:
<form method="post" action="ListTest2.jsp">
<p> Services:<br>
<input type="checkbox" checked
name="selected" value="0">
E-Mail
<input type="hidden" name="service0"
value=" E-Mail "> <br>
<input type="checkbox" checked
name="selected" value="1">
Web Hosting
<input type="hidden" name="service1"
value=" Web Hosting "> <br>
<input type="checkbox" checked
name="selected" value="2">
E-Commerce
<input type="hidden" name="service2"
value=" E-Commerce "> <br>
<p> <input type="submit" value="Select">
</form>
ListTest2.jsp 頁面處理用戶提交的表單數據,創建并顯示選定服務的列表:
<!-- ListTest2.jsp -->
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="u" uri="/WEB-INF/util.tld" %>
<u:list var="selectedServices">
<c:forEach var="index" items="${paramValues.selected}">
<c:set var="paramName" value="service${index}"/>
<u:item value="${param[paramName]}"/>
</c:forEach>
</u:list>
<p> Selected Services:<br>
<ul>
<c:forEach var="service" items="${selectedServices}">
<li><c:out value="${service}"/></li>
</c:forEach>
</ul>
總結
無論 JSTL 和其他標記庫提供了多少標記,總會有一些情形需要開發自己的標記。本文演示了如何使用簡單標記 API 以及在開發定制標記時所需的基本技巧。此外,還提供了幾個可以供您在 Web 應用程序中重復使用的標記。
Andrei Cioroianu (devtools@devsphere.com) 是 Devsphere (www.devsphere.com) 的創始人,那是一家 Java 框架、XML 咨詢和 Web 開發服務的供應商。Cioroianu 編寫了許多 Java 文章,這些文章由 ONJava (www.onjava.com)、JavaWorld (www.javaworld.com) 和 Java Developer's Journal 出版
。他還與別人合著了 Java XML Programmer's Reference 和 Professional Java XML 兩本書(均由 Wrox Press 出版)。
posted on 2005-06-07 16:27
似水流年 閱讀(493)
評論(0) 編輯 收藏 所屬分類:
JSP/Servlet