....................................
........我想实验下forEach在select中的应用,不过没成功,还在试....................
- 使用 JSP 2.0 开发类似 JSTL 的标记
- 学习如何使用简单标记 API 和构建用于求解 JSP 表达式的定制标记,如何控制 JSP 页面中的流以及如何创建 Java 集合。
- 本文相关下载:
- · 示例代码
- · OC4J 10g 开发人员预览版 2
- · JSTL 1.1
- 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 exportexported = 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&nb