简化 Ajax 和 Java 开发,第 4 部分: 使用 JSP 标记文件创建 JSF 样式的组件

来源:互联网 发布:虚拟天文馆软件 编辑:程序博客网 时间:2024/05/16 13:00

http://www.ibm.com/developerworks/cn/web/wa-aj-simplejava4.html

简介: JavaServer Pages (JSP) 和 JavaServer Faces (JSF) 过去都使用 Expression Language (EL) 的不同变体。它们在 JSP 2.1 中的统一提供了新的机会,允许您在定制 JSP 标记中使用 deferred values 和 deferred method 属性。本文展示了如何开发基于 JSP 标记文件的 Java™ Web 组件,它的构建比 JSF 组件更加简单和容易。

JSP 和 JSF 是当今构建 Web 应用程序时使用的最重要的 Java 标准。但是这两种技术的基本原理却截然不同。JSP 采用的底层机制很简单,您可以控制发生的操作,并且可以选择自己的方式。而 JSF 则复杂一些,它处理开销,但这个应用程序模型是标准化的,这意味着工具能做更多的工作、供应商能够提供组件库,使开发人员能够专注于构建应用程序。

Ajax 资源中心

请访问 Ajax 资源中心,这是有关 Ajax 编程模型信息的一站式中心,包括很多文档、教程、论坛、blog、wiki 和新闻。任何 Ajax 的新信息都能在这里找到。

就像本系列的 第 2 部分  第 3 部分 一样,您不需要一个非常复杂的框架来构建 Web 表单。如果您希望保持简单,或希望快速开发 Web 应用程序,那么可以将 JSP Tag Library (JSTL) 和标记文件、动态属性结合起来使用。这样,您将能够轻松定制组件,控制 HTML 输出和 HTTP 请求。

如果要构建复杂的 Web 应用程序,正确的选择是将 JSF 框架和良好的组件库结合使用。因为 Web 框架不可能满足每个应用程序的所有需求,所以您还得构建一些自己的组件。在这种情况下,您可以使用本文给出的技术创建与 JSF 类似的组件,而不需要使用 JSF API。此外,还可以在同一 Web 页面上混合使用非 JSF 组件和 JSF 组件,大大避免了 JSF 带来的复杂性。

使用 deferred values

表达式语言最初是 JSTL 的一个主要特性,后来合并到了 JSP 2.0 标准。早期的 EL 版本允许使用 ${...} 表达式查询 JavaBeans 和集合,JSP 容器在 JSP 页面的执行期间计算这个表达式。

JSF 框架定义了它自己的 EL 变体,从而支持它的 #{...} 表达式的其他功能,比如延迟求值和设置 EL 表达式的值。这些特性实现了数据绑定,并允许 JSF 框架在表达式计算完毕后进行控制。

JSP 2.1 标准统一了这两种 EL 变体。您仍然可以使用 ${...} 表达式,它的计算时间由 JSP 容器决定。但现在也可以在定制 JSP 标记的属性内部使用 #{...} 表达式,它可以不基于 JSF。这一节展示如何对非 JSF 标记使用 deferred-value 表达式,它比 JSF 表达式更容易构建。

创建和配置标记处理函数

JSP 2.1 允许在任何定制标记的属性内使用 #{...} 表达式,前提是属性的 setter 方法包含一个 javax.el.ValueExpression 参数。清单 1 展示了定制标记的处理函数是什么样的。它通常扩展 javax.servlet.jsp.tagext 包的 SimpleTagSupport 类,并且对于定制标记的每一个属性,它必须有一个 set 方法。此外,标记处理函数应该提供一个 doTag() 方法,当执行包含定制标记的 JSP 页面时调用这个方法。


清单 1. deferred-value 属性的 setter 方法

package mylib;public class MyTag extends javax.servlet.jsp.tagext.SimpleTagSupport {    private javax.el.ValueExpression myAttr;    public void setMyAttr(javax.el.ValueExpression myAttr) {        this.myAttr = myAttr;    }    public void doTag() throws javax.servlet.jsp.JspException,                                java.io.IOException {        ...    }}


 

在标记库描述符(TLD)文件中定义定制标记,这个文件不仅包含标记库的版本、前缀、URI 和每个定制标记的定义,它还指定了标记名、标记处理函数类、正文内容的类型和标记的属性。对于 javax.el.ValueExpression 类型的每个属性,必须将 <deferred-value> 包含在 <attribute> 元素内部,如清单 2 所示:


清单 2. 定义定制标记及其 deferred-value 属性

<?xml version="1.0" encoding="UTF-8" ?><taglib xmlns="http://java.sun.com/xml/ns/javaee" ... version="2.1">    <tlib-version>1.0</tlib-version>    <short-name>mylib</short-name>    <uri>/mylib.tld</uri>    <tag>        <name>myTag</name>        <tag-class>mylib.MyTag</tag-class>        <body-content>scriptless</body-content>        <attribute>            <name>myAttr</name>            <deferred-value>                <type>...</type>            </deferred-value>        </attribute>        ...    </tag></taglib>


 

如果一个 JSP 页面包含 <mylib:myTag myAttr="#{...}">,则 JSP 容器将该表达式包装在一个 javax.el.ValueExpression 对象中,这个对象传递给标记处理函数实例的 setMyAttr() 方法,这个实例的 doTag() 方法能够使用 EL API 来计算表达式并更改它的值(参见清单 3):


清单 3. 使用 javax.el.ValueExpression 的 doTag() 方法

package mylib;public class MyTag extends javax.servlet.jsp.tagext.SimpleTagSupport {    private javax.el.ValueExpression myAttr;    ...    public void doTag() throws javax.servlet.jsp.JspException,                                java.io.IOException {        javax.el.ELContext elContext = getJspContext().getELContext();        Object value = myAttr.getValue(elContext); // get current value        value = ...                                // change value        if (!myAttr.isReadOnly(elContext))            myAttr.setValue(elContext, value);     // set new value    }}


 

如果查看 JSP 容器从 JSP 页面生成的 Java 源代码,您将看到属性值是如何传递到标记处理函数实例的 setter 方法的。在这之后,将调用处理函数的 doTag() 方法来执行定制标记。这是 JSP 页面中的定制标记背后的工作机制。

清单 3 中的代码使用 EL API 处理 deferred-value 表达式。javax.el.ValueExpression  getValue() 方法在给定的 EL 上下文中计算表达式,如果不能设置表达式的值,isReadOnly() 将返回 true,并且 setValue() 方法将更改已在 EL 表达式中指定的 bean 属性的值。

例如,如果一个 JSP 页面包含 <mylib:myTag myAttr="#{someBean.someProp}">,清单 3 中的 getValue() 调用将返回属性的值,并且 setValue() 将更改 bean 属性的值。只有 bean 类的属性没有 set 方法时,isReadOnly() 方法才会返回 true

在 JSP 标记文件中使用 EL API

如在前面的例子见到的一样,构建属性接受 deferred-value 表达式的定制标记是相当简单的。如果开发的是 JSP 标记文件,定制标记实现就更加简单了,因为 JSP 容器会为您生成标记处理函数类,从而不必在 TLD 文件中配置定制标记。您只需在标记文件中使用<%@ attribute name="myAttr" deferredValue="true" %> 指令,其余的由 JSP 容器处理。

可以在没有脚本的 JSP 页面中使用 set.tag 文件(参见清单 4)来设置 #{...} 表达式的值。JSP 容器将生成一个标记处理函数类,所有 3 个属性都具有 setter 方法:exprvalue  type。生成的类的 doTag() 方法将包含 <%  %> 之间的 Java 代码。这个代码从 JSP 上下文中获取属性值,将给定的 value 转换成指定的 type,然后调用表达式对象的 setValue() 方法。


清单 4. set.tag 文件

<%@ attribute name="expr" required="true" deferredValue="true" %><%@ attribute name="value" required="true" rtexprvalue="true"    type="java.lang.Object"%><%@ attribute name="type" required="false" rtexprvalue="true" %><%@ tag body-content="empty" %><%    javax.el.ELContext elContext = jspContext.getELContext();    javax.el.ValueExpression valueExpr =        (javax.el.ValueExpression) jspContext.getAttribute("expr");    Object valueAttr = jspContext.getAttribute("value");    String valueType = (String) jspContext.getAttribute("type");        if (!valueExpr.isReadOnly(elContext)) {        Class valueClass = null;        if (valueType != null && valueType.length() > 0)            valueClass = Class.forName(valueType);        else            valueClass = valueExpr.getExpectedType();        javax.servlet.jsp.JspFactory jspFactory            = javax.servlet.jsp.JspFactory.getDefaultFactory();        valueAttr = jspFactory.getJspApplicationContext(application)            .getExpressionFactory().coerceToType(valueAttr, valueClass);        valueExpr.setValue(elContext, valueAttr);    }%>


 

考虑到兼容性,默认情况下,标记文件使用 JSP 2.0 版本。因为 deferred-values 是 JSP 2.1 的特性,因此必须在文件implicit.tld(见清单 5)中指定正确的 JSP 版本。这个文件必须放置在每个包含使用 JSP 2.1 特性的标记文件的目录中。


清单 5. implicit.tld 文件

<taglib>    <jsp-version>2.1</jsp-version></taglib>


 

本文的示例应用程序有两个包含标记文件的目录:

  • /WEB-INF/tags/dynamic/comp 对一些可以在 JSP 页面中使用的 Web 组件进行了分组:
    • inputText.tag
    • inputData.tag
    • action.tag
  • /WEB-INF/tags/dynamic/comp/util 包含从 Web 组件调用的 helper 标记文件:
    • set.tag
    • invoke.tag

构建一个简单的 Web 组件

下面的示例使用 set.tag 文件构建一个输入组件。inputText.tag 文件用 <%@attribute%> 指令(参见 清单 6)声明了两个属性。name属性具有 rtexprvalue="true",因此可以在这个属性的值内使用 ${...} 表达式。对于 value 属性,可以使用 #{...} 表达式,因为它的 <%@attribute%> 指令包含 deferredValue="true"。这个标记文件也包含了 <%@ tag dynamic-attributes="dynAttr" ... %>,它允许您提供其他属性,这些属性存储在一个名为 dynAttr 的 Java Map 中。

使用 <%@taglib%> 指令导入已使用标记库之后,如果 HTTP 请求包含具有给定 name 的参数,inputText.tag 文件将通过 <dcu:set> 调用 set.tag。如果存在,这个参数的值将存储到 value 属性内部指定的 bean 属性中。例如,如果在 JSP 页面中使用 <dc:inputText name="someParam" value="#{someBean.someProp}" .../>someParam 请求参数的值将被存储到 bean 实例的 someProp 属性中。

接下来,inputText.tag 文件生成一个具有给定 name  value 属性的 <input type="text"> 元素。<input> 元素的 value 属性将包含 bean 属性的值。来自 dynAttr 映射的动态属性也添加到 <input> 元素。例如,如果 JSP 页面包含 <dc:inputText ... class="someClass"/>,生成的 HTML 元素将包含 class 属性。


清单 6. inputText.tag 组件

<%@ attribute name="name" required="true" rtexprvalue="true" %><%@ attribute name="value" required="true" deferredValue="true" %><%@ tag dynamic-attributes="dynAttr" body-content="empty" %><%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %><%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %><%@ taglib prefix="dcu" tagdir="/WEB-INF/tags/dynamic/comp/util" %><c:if test="${param[name] != null}">    <dcu:set expr="#{value}" value="${param[name]}"/></c:if><input type="text" name="${fn:escapeXml(name)}" value="${fn:escapeXml(value)}"    <c:forEach var="attr" items="${dynAttr}">        <c:if test="${attr.key != 'type'}">            ${attr.key}="${fn:escapeXml(attr.value)}"        </c:if>    </c:forEach>>


 

清单 7 展示了如何在 JSP 页面中使用 <dc:inputText> 调用 inputText.tag 文件。首先,必须使用 <%@taglib%> 指令导入标记文件库。然后,通常会通过 <jsp:useBean> 创建或包含一个 bean 实例。<dc:inputText> 组件应该置于 <form> 元素的内部。在本例中,生成的 <input> 元素将包含 bean 属性的值。当用户单击 Submit 按钮时,表单数据将发送到服务器,在这里,inputText.tag 文件通过 <dcu:set> 将用户输入存储到 bean 属性。


清单 7. 在 JSP 页面中使用 <dc:inputText>

<%@ taglib prefix="dc" tagdir="/WEB-INF/tags/dynamic/comp" %><jsp:useBean id="someBean" class="someapp.SomeBean" scope="request"/><form method="POST">    <dc:inputText name="someParam" value="#{someBean.someProp}"        class="someClass" size="20" maxlength="40"/>    <input type="submit" value="Submit"></form>


 

使用 deferred methods

最初的 EL 变体是 JSTL 1.0 的一部分,没有任何从 JSP 页面调用 Java 方法的机制。当 EL 成为 JSP 2.0 的一部分时,它的语法得到了增强,支持映射到静态 Java 方法的 EL 函数。但在多数情况下,您需要调用 JavaBean 对象的实例方法。

认识到有必要为 Web 组件指定操作和验证方法后,JSF EL 变体添加了一个允许在属性内使用方法表达式的特性。JSP 2.1 统一后的 EL 对所有定制 JSP 标记(无论是否使用 JSF API)启用这个特性。本节展示如何将 deferred-method 表达式用于非 JSP 标记,它比 JSF 标记简单多了。

通过 EL API 调用一个方法

如果一个定制标记的属性必须接受一个方法表达式,则属性 setter 的参数必须使用 javax.el.MethodExpression 类型,如 清单 8 所示。如果在 Web 页面中使用 <mylib:myTag methodAttr="#{someBean.someMethod}">,JSP 容器将把表达式包装到传递给setMethodAttr() 的对象。

然后,JSP 容器将调用标记处理函数的 doTag() 方法,从这里可以调用 bean 方法,将 EL 上下文和一个参数数组传递到javax.el.MethodExpression  invoke() 方法。通过使用 JSP 上下文的 setAttribute() 方法创建一个 JSP 变量,您可以使返回值在 JSP 页面上可用。


清单 8. 使用 javax.el.MethodExpression

package mylib;public class MyTag extends javax.servlet.jsp.tagext.SimpleTagSupport {    private javax.el.MethodExpression methodAttr;    public void setMethodAttr(javax.el.MethodExpression methodAttr) {        this.methodAttr = methodAttr;    }     public void doTag() throws javax.servlet.jsp.JspException,                                java.io.IOException {        javax.el.ELContext elContext = getJspContext().getELContext();        Object paramArray[] = new Object[] {...};        Object returnValue = methodAttr.invoke(elContext, paramArray);        getJspContext().setAttribute("returnVar", returnValue);    }}


 

当构建您自己的 Java 标记处理函数支持的定制标记时,必须在 TLD 文件中配置它们(参见清单 9)。方法属性必须用 <deferred-method> 元素进行标记,该元素的 <method-signature> 子元素应该包含已调用的 bean 方法的签名。


清单 9. 定义定制标记及其 deferred-method 属性

<?xml version="1.0" encoding="UTF-8" ?><taglib xmlns="http://java.sun.com/xml/ns/javaee" ... version="2.1">    <tlib-version>1.0</tlib-version>    <short-name>mylib</short-name>    <uri>/mylib.tld</uri>    <tag>        <name>myTag</name>        <tag-class>mylib.MyTag</tag-class>        <body-content>scriptless</body-content>        ...        <attribute>            <name>methodAttr</name>            <deferred-method>                <method-signature>...</method-signature>            </deferred-method>        </attribute>    </tag></taglib>


 

懂得如何像清单 8 和清单 9 那样实现定制标记非常有用,但是开发 JSP 标记文件更加容易,因为 JSP 容器为您生成标记处理函数类,因此不再需要在 TLD 文件中配置定制标记。您只需要在标记文件使用 <%@attribute%> 指令,如清单 10 所示:


清单 10. 在 JSP 标记文件中定义 deferred-method 属性

<%@ attribute name="methodAttr" deferredMethod="true"     deferredMethodSignature="..." %>


 

因为同一个类的多个方法可以使用相同的名称,所以 JSP 容器需要完整的签名来识别需要调用的方法。但是,如果想构建可用于任何方法的泛型定制标记(不管其签名是什么样子),您则可以将 javax.el.MethodExpression 视为常规的 Java 类。

invoke.tag 文件(参见 清单 11)定义一个 javax.el.MethodExpression 类型的 method 属性。如果在 JSP 变量中已经有这个类的一个实例,可以通过 <dcu:invoke method="${methodVar}" .../> 将它传递到标记文件。这个定制标记还有一个可选的 params 属性,它可以是一个单一的对象或一个对象数组。这些参数传递到表达式对象的 invoke() 方法。

返回值存储到 varAlias 变量中,该变量通过 <%@variable%> 指令导出。例如,如果 JSP 页面包含 <dcu:invoke method="${methodVar}" params="${paramArray}" var="returnVar"/>invoke.tag 文件将通过给定的参数调用这个方法,并将返回值存储到 returnVar 中。


清单 11. invoke.tag 文件

<%@ attribute name="method" required="true" rtexprvalue="true"    type="javax.el.MethodExpression" %><%@ attribute name="params" required="false" rtexprvalue="true"    type="java.lang.Object"%><%@ attribute name="var" required="true" rtexprvalue="false" %><%@ variable name-from-attribute="var"    variable-class="java.lang.Object"    alias="varAlias" scope="AT_END" %><%@ tag body-content="empty" %><%    javax.el.ELContext elContext = jspContext.getELContext();    javax.el.MethodExpression methodExpr =        (javax.el.MethodExpression) jspContext.getAttribute("method");    Object paramArray = jspContext.getAttribute("params");    if (paramArray == null)        paramArray = new Object[0];    else if (!(paramArray instanceof Object[]))        paramArray = new Object[] { paramArray };    Object returnValue = methodExpr.invoke(        elContext, (Object[]) paramArray);    jspContext.setAttribute("varAlias", returnValue);%>


 

从 JSP 页面调用 action 方法

JSF 框架将 action 方法附加到命令按钮。这些方法没有参数,单击 Web 浏览器的提交按钮之后它将在服务器端被调用。现在可以通过 deferred-method 特性的帮助(已添加到 JSP 2.1),从非 JSF 页面调用 action 方法。您已经知道如何在 invoke.tag 文件中使用 EL API,在 action.tag(参见清单 12)中使用该文件调用 action 方法:


清单 12. action.tag 组件

<%@ attribute name="method" required="true" deferredMethod="true"    deferredMethodSignature="String action()" %><%@ attribute name="var" required="true" rtexprvalue="false" %><%@ variable name-from-attribute="var"    variable-class="java.lang.String"    alias="varAlias" scope="AT_END" %><%@ tag body-content="empty" %><%@ taglib prefix="dcu" tagdir="/WEB-INF/tags/dynamic/comp/util" %><dcu:invoke method="${method}" var="varAlias"/>


 

清单 13 演示了如何在常规的 JSP 页面中使用 <dc:action> 标记调用 action 方法。通常在处理 POST 请求期间调用这些方法。在这个示例中,action 方法返回的结果存储在一个名为 outcome 的 JSP 变量中。


清单 13. 在 JSP 页面中使用 <dc:action>

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %><%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %><%@ taglib prefix="dc" tagdir="/WEB-INF/tags/dynamic/comp" %><jsp:useBean id="someBean" class="someapp.SomeBean" scope="request"/><c:if test="${fn:toUpperCase(pageContext.request.method) == 'POST'}">    <dc:action method="#{someBean.someAction}" var="outcome"/>    ${outcome}</c:if><form method="POST">    <input type="submit" value="Submit"></form>


 

为输入组件添加验证器属性

前面的示例(参见 清单 6)演示了如何构建一个输入组件。inputData.tag 文件(参见清单 14)改进了组件,添加了 valueType validator 属性,它们用于转换和验证用户输入。valueType 属性会传递到 <dcu:set> 标记,该标记负责数据的转换。

validator 方法将用户输入作为参数,并且可能会返回一条错误消息,保存在一个名为 validationError 的 JSP 变量中。validator方法通过 <dcu:invoke>  inputData.tag 文件调用。


清单 14. inputData.tag 组件

<%@ attribute name="name" required="true" rtexprvalue="true" %><%@ attribute name="value" required="true" deferredValue="true" %><%@ attribute name="valueType" required="false" rtexprvalue="true" %><%@ attribute name="validator" required="false" deferredMethod="true"    deferredMethodSignature="java.lang.String validate(java.lang.String)" %><%@ variable name-given="validationError" scope="AT_BEGIN"    variable-class="java.lang.String"  %><%@ tag dynamic-attributes="dynAttr" body-content="scriptless" %><%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %><%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %><%@ taglib prefix="dcu" tagdir="/WEB-INF/tags/dynamic/comp/util" %><c:remove var="validationError"/><c:if test="${param[name] != null and validator != null}">    <dcu:invoke method="${validator}" params="${param[name]}" var="validationError"/></c:if><c:if test="${param[name] != null and validationError == null}">    <dcu:set expr="#{value}" value="${param[name]}" type="${valueType}"/></c:if><jsp:doBody/><input type="text" name="${fn:escapeXml(name)}"        value="${fn:escapeXml(!empty param[name] ? param[name] : value)}"    <c:forEach var="attr" items="${dynAttr}">        <c:if test="${attr.key != 'type'}">            ${attr.key}="${fn:escapeXml(attr.value)}"        </c:if>    </c:forEach>>


 

inputData.tag 文件使用 <jsp:doBody/> 执行 JSP 页面中(参见清单 15)置于 <dc:inputData>  </dc:inputData> 之间的内容。这允许 JSP 页面在标记文件生成的 <input> 元素之前显示验证错误。验证错误 也可以显示在 <input> 之后。


清单 15. 在 JSP 页面中使用 <dc:inputData>

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %><%@ taglib prefix="dc" tagdir="/WEB-INF/tags/dynamic/comp" %><jsp:useBean id="someBean" class="someapp.SomeBean" scope="request"/><form method="POST">    <dc:inputData name="someParam" value="#{someBean.someProp}"            validator="#{someBean.someValidator}"            class="someClass" size="20" maxlength="40">        <c:out value="${validationError}"/>    </dc:inputData>    <c:out value="${validationError}"/>    <input type="submit" value="Submit"></form>


 

结束语

通过这个系列的文章,您学习了如何通过使用 JSP 标记文件、JSP EL、JSTL、动态属性、代码生成器、约定和 JavaScript 对象层次结构,简化 Ajax 和 Java 开发。在本系列的最后一部分,您学习了如何在定制 JSP 标记中使用 deferred values 和 deferred methods 来创建 JSF 样式的组件。

 代码地址:http://download.csdn.net/detail/mr__fang/5091095