Posted on 2006-12-21 10:11
冰浪 阅读(747)
评论(0) 编辑 收藏 所属分类:
Frameworks
注:此文为转载自dev2dev:http://dev2dev.bea.com.cn/bbs/thread.jspa?forumID=121&threadID=35942
最新翻译的:
JSF1.2技术的新特性总结
本文的主要内容:
“Web Tier to Go With Java EE 5”系列文章中,“JSF1.2新特性”是第一篇。就像在文中表述的一样,JSP技术的优势随着JSF技术的发展得到了进一步的扩大,这种优势是通过统一EL表达式建立起来的。统一JSP的框架也是JSF1.2技术中众多重要成绩之一,并在JSR252规范下进行开发。除了这些变化为,JSF技术还贡献了大量的重要的容易使用的特性。这篇文章简要的描述一些重要的基本特性,大概有如下几点:
* 统一JSP技术
* 改进自定义信息的支持
* 提供状态保存特性
* 能够关闭自动生成组件客户端ID
* 新的setPropertyActionListener标签
《JavaServer Faces规范》的前言部分提供了一组完成的特性描述。
统一JSP技术:
JSP和JSF技术相对立的核心是它们之间的页面生命周期的不同。在以前的文章中已经介绍了JSP规范,JSP规范指出:页面的元素被执行显示时,JSP支持单一的“渲染-响应”生命周期, 然后立即渲染页面。JSF的生命周期分成了多个阶段,当一个组件树被创建完毕,组件的数据就会被处理,然后组件被渲染并显示在页面上。然而,一个JSF页面并不需要在执行后立即渲染。因此,在JSP页面上使用的JSF组件被渲染时,有时候并不是按照正常的顺序完成,而且会丢失组件的一些状态或者其他的问题产生。
这么文章涉及到JSP2.1的新特性。这里会从一下3个方面详细的介绍JSF技术的改进的地方:
* 首先JSF组件支持JSTL的forEach标签
* 改进tree组件创建方法和内容组织
* 反对只使用JSF技术的EL,赞成使用统一的EL
首先JSF组件支持JSTL的ForEach标签就像在描述JSP技术新特性的文章中表述的一样,Java EE web层中的统一EL的一个新特性就是:迭代标签,例如JSTL的forEach能够在JSF组件中直接使用。例如,使用输入组件构建一个简单的表格,可以通过下列代码来实现:
<table>
<tr>
<th>Item Name</th>
<th>Item Price</th>
<th>Item Quantity</th>
</tr>
<c:forEach var="item" items="#{shoppingCart.items}">
<tr>
<td><h:outputText value="#{item.name}" /></td>
<td><h:outputText value="#{item.price}" /></td>
<td><h:inputText value="#{item.quantity}" /></td>
</tr>
</c:forEach>
<h:commandButton value="update quantities" action="update" />
</table>
没有统一使用EL, 这几乎是不可能实现的。
改进tree组件创建方法和内容组织
在Hans Bergsten的《article about the use of JavaServer Faces technology with JSP pages》文中写到的,混合使用JSP代码和JSF标签有时候会产生预期不到的结果。例如,在下面的代码中,JSF 组件outputText输出的文本内容“Hi”本来应该在文本内容“what’s your name?”前面显示:
<h:panelGroup>
<h:outputText value="Hi. "/>
What's your name?
</h:panelGroup>
然而,这两行代码的输出是顺序是相反的。是什么导致了问题的发生?JSF的实现是以JSP的渲染引擎来渲染组件树的。因为JSP会立即执行并把静态文本内容放在response中,而<panelGroup>的子节点标签直到相应的结束标签</panelGroup>匹配后才会被渲染,所以“what’s your name?”就会在“Hi”之前显示了。
像这样的问题在Hans Bergsten的文中已经提到并在JSF1.2版本中得到了修改。解决方案包括:怎样改变组件树的创建、各种内容的处理和渲染等等。最重要的改变如下几点:
* 创建和渲染组件树被分成2步,为了防止JSP引擎过早的渲染页面内容。
* 所有的渲染器的rendersChildren属性值现在已经设计为“true”,目的时防止联合组件的内容在各自的组件内部分别渲染。
* 标签内部的静态内容对应的渲染器的rendersChildren属性值设置为true,目的是获取并保存一个短暂的UIOutput组件并把该组件添加到组件树中。
* 状态管理器从ViewTag中移到了ViewHandler中,为了防止write State在实际保存过程中被调用。
在接下来的“Web Tier to Go With Java EE 5”系列文章中会提供更多的关于描述组件树的创建和内容组织的细节,这就让JSF标签组件中使用JSP代码成为了可能。
反对只使用JSF的EL,赞成使用统一的EL
如本文前面的内容介绍,JSP和JSF的EL表达式会整合成统一的EL。这就意味着现在的JSF的EL是不推荐使用的。在介绍统一的EL前已经使用了JSF开发的应用,请务必做好具有向后兼容的工作。
然而,为了利用统一EL的特性,你可能需要改变原来自定应的组件和自定义的标签。要移植到统一EL,首先要把ValueBinding换成ValueExpression,把MethodBinding换成MethodException。这些改变都相对的容易,而且还有更好的办法可以完成这些改变。例如,因为所有标准的标记属性都可以访问值表达式,你的标记处理程序不需要检测属性是可以接收、访问一个值表达式。
要了解更多关于如何移植到统一EL的内容,请参见文章“Unified Expression Language”(http://java.sun.com/products/jsp/reference/techart/unifiedEL.html)
改进易用性来支持自定义信息
JSF1.2技术的一个重要改进就是在JSF应用中增加自定义信息的能力。这个改进包括如下几个方面:
* 一组新的标准转换信息
* 对输入组件提供了新的属性:requiredMessage、converterMessage、validatorMessage
* 为输入组件提供了新的label属性,允许组件的名字中包含错误信息
* 提供新的资源绑定元素在应用中绑定资源。
一组标准的转换器信息
以前的JSF给标准验证器提供了一组标准的错误信息。版本1.2增加了标注转换器提供了一组错误信息。要查看这些信息,请参考JSF1.2规范(http://jcp.org/en/jsr/detail?id=252)的第2.5.2.4节。
新属性:requiredMessage,converterMessage,validatorMessage
很多情况下,标准的错误提示信息很适合为我们的需要。如果不合适,我们可以使用自定义的信息来覆盖这些标准的信息,要完成个目的,就是通过输入组件的新属性requiredMessage、converterMessage、validatorMessage来完成。
当你需要修改信息时,新的特性允许你覆盖原有的信息。例如,假设你在userid和password2个域中使用了验证长度的验证器。你希望一个错误信息提示“Userid必须时9个字符”,另一个“Password必须是9个字符”,但是你希望对这2个输入域使用同一个验证器。通过validatorMessage属性,你可以在适当的组件上设置特殊的信息,就不用为每个验证器实例提供单独的提示信息。
这些属性接收字符型的值以及值表达式,和JSF的标记属性一样的工作原理。因此,你可以使用值表达式来引用绑定资源中的信息,如下面的例子:
<h:inputText value="#{customer.userID}"
validatorMessage="#{customMessages.userIdMessage}" >
<f:validateLongRange minimum="9" maximum="9"/>
</h:inputText>
就像你猜想的一样,输入组件的属性requiredMessage值覆盖了默认的信息提示。输入组件的属性converterMessage值覆盖了默认的转换信息提示。同样的,输入组件的属性validatorMessage值也覆盖了默认的验证信息提示。
因为这种特性,页面开发人员现在可以更为合适的提供错误的提示信息。这样,用户能够更准确的直到错误的根源。
输入组件的新属性:label
输入组件的label新属性是另外一个帮助用户确认错误信息的重要特性。许多由组件产生的标准错误信息包括根据参数得到的信息。输入组件的label属性值是用来适当取代相关的默认信息的。例如,下面的信息是与DateTimeConverter相关的信息,用DATE_ID标识:
{2}: "{0}" could not be understood as a date.
这就是说,你有一个DateTimeConverter实例,并与一个文本输入框绑定。设置输入组件的label属性为BirthDate。如果用户在文本输入框中输入“older than the hills”,转换器就失败了。当页面再次被渲染的时候,用户就能看到如下的信息提示:
Birth Date: "older than the hills" could not be understood as a date.
Label组件接受文本内容,也接受值表达式。所以,在使用其他的JSF标签属性时,你可以使用表达式来引用绑定的资源文件内容。
新的资源配置元素
JSF1.2版本以前的规范,页面开发人员使用loadBundle标签来引用资源文件,从而达到本地化的目的。在新的1.2版本中,页面开发人员一样可以在页面中使用资源绑定。
更高效率的装载绑定好的资源,包括在应用系统的配置文件中使用资源绑定元素进行注册资源的绑定,新的规范提供了一个很好的入口方式。下面的资源绑定元素注册一个叫做“CustomMessages的ResourceBundle类,这个类存在应用系统的资源包里:
<resource-bundle>
<var>customMessages</var>
<base-name>resources.CustomMessages</base-name>
</resource-bundle>
var子元素定义了资源引用名称,页面开发人员在开发页面时可以采用如下的方式进行调用:
<h:outputText value="#{customMessages.myText}" />
在这个例子中,myText就是定义在资源文件中,我们要引用的信息的键(key)。
通过这个新的资源绑定元素,我们可以在应用系统中绑定更多的资源文件。这样做,新的元素的使用,不仅消除了在多个页面使用“loadBundle”绑定资源的方式,而且大大提高了性能,因为装载资源是很“昂贵”的操作。
提供状态保存特性
JSF1.2版本在状态管理方面主要有2个变化。一个变化是,当应用系统使用多个框架(frame)或者窗口(window)时,整合系统状态的不稳定问题;另一个变化是提供了客户端安全状态操作。
在多框架或者多窗口的应用中系统状态不稳定的原因是这些复杂逻辑试图会把根视图的ID重复复制,因此状态管理就变得混乱了。在1.2版本中做了一些修改,提供了一些状态管理的API来解决这个问题。
首先,在视图中的每一个窗口(Window)或者框架(Frame)都有唯一的ID标识,这个ID标识是视图根ID和一个随即数据联合组成,并保存在一个隐藏域中,用来连接窗口(Window)或者框架(Frame)。ViewHandler中的writeState方法已经被修改,并用来生成唯一的ID,并且在JSF的“渲染-回应”阶段输出到客户端。另外,encodeEnd方法中,UIForm也被修改,让它在输出“闭合标签”到客户端的这个过程之前调用writeState方法,以便多表单(form)的状态被保存下来。
同时,在物理“渲染-回应“阶段,StateManager提供了最新的saveSerializedView方法会使用这个唯一的ID把对应的视图序列化到Session中。例如:如果服务器崩溃了,这些被保存的状态就能在另一个服务器中再次使用。所以,“状态-保存”机制在JSF1.2版本在中被高度有效的支持。最后,ViewHandler类中的restoreView方法可以使用唯一的ID来标识恢复视图,即使是在“恢复视图”阶段。
另一个重要的规定,就是允许对客户端进行加密,包括在发送一个关闭标签到客户端之前。你可以选择声明的方式对客户端进行加密:使用ClientStateSavingPassword这个类作为加密方式的入口,如下所示:
<env-entry>
<env-entry-name>
com.sun.faces.ClientStateSavingPassword
</env-entry-name>
<env-entry-value>somePassword</env-entry-value>
<env-entry-type>java.lang.String</env-entry-type>
</env-entry>
你提供的密码通常是生成 值/对的方式进行加密。如果这个环境入口没有在配置文件中定义,这个加密方法就不会被执行。
关闭自动生成客户端组件ID的功能
JSF采用一种算法来生成客户端组件(包含在Form中)的表示号(ID),客户端组件的ID如下面的形式:
[form ID]:[clientID]
页面开发人员可以对指定ID的form组件或者包含在该form中的其他组件进行操作。如果页面开发人员没有指定form的ID号或者组件的ID号,那么系统会自动生成。
早期版本的JSF规范没有规定这个算法,只要开发人员理解其工作原理就够了。此外,对于自定义的组件或者一个渲染类来说,页面开发人员没办法确定是否已经设置了组件的客户端ID号或者是否系统为它们自动生成。最后,因为某种原因,渲染器的开发人员可能无法改变它的名称,但是仍然要在JavaScript中引用它。
最后的JSF规范明确的说明了生成客户端组件ID的算法。我们可以在UIComponent的API文档中找到相关信息。由于UIForm组件增加了prependId属性,相应的算法也做了变化。页面开发人员现在可以改变该属性的默认值(默认值为false,可以改为true),这样就表明可以不需要使用Form的ID作为组件ID的前缀了。下表格显示了正常情况下生成客户端组件ID的不同点:
Table 1:客户端组件ID生成情况
场景 prependId=rue prependID=alse
开发人员没有设置ID _id0:_id1 _id1
开发人员仅仅设置了form的ID myForm:_id0 _id0
开发人员只设置组件ID _id0:myComponent myComponent
开发人员对form和组件都设置了ID myForm:myComponent myComponent
新标签setPropertyActionListener
除了actionListener标签之外,允许我们给自定义的组件注册一个Action Listener,核心的标记库现在已经包括在setPropertyActionListener标记中。用这个标记注册一个特定的action listener到ActionSource实例中,并与具体的组件关联起来。当组件被激活,listener就会把对象的引用存储到标记的属性中。
为了展示标记的用处,假设我们有一个forEach标记,并且在其中使用了commandButton,以及使用iterator来显示一组书的信息,代码如下:
<c:forEach items="#{bookDBAO.books}" var="book" varStatus="stat">
<c:set var="book" scope="request" value="${book}"/>
...
<h:commandButton id="add" action="#{catalog.add}"
value="#{bundle.CartAdd}">
<f:setPropertyActionListener
target="#{requestScope.book}"
value="#{book}"/>
</h:commandButton>
<c:remove var="book" scope="request"/>
</c:forEach>
当用户点击button,选择的book就会增加到购物车中,并且显示下一页。
如果你熟悉JSTL,那你就直到forEach的var属性是在本页面那有效的。然而,book的数据需要在request范围内有效,这样显示页面(点击后转到的下一页面)才能范问到book的数据。因此,setPropertyActionListener标签通常用来把当前(book)对象设置为request访问范围内。
在这个例子中,setPropertyActionListener标签的value属性指的是“book”对象;target属性是表达式“requestScope.book”的引用,并且和commandButton组件建立了关联。
总结
这篇文章只是JSF1.2规范的简要说明,介绍一些新的特性和常见问题的解决方法。我们建议大家使用一下JSF1.2。如果你有一些问题或者看法,请发邮件(users@javaserverfaces.dev.java.net)给我们或者加入我们的社区(http://java.sun.com/j2ee/javaserverfaces)。同时,期待下一篇文章在我们的网站上出现。