适用于云环境的 JSF 2.0,第 2 部分

作者:Deepak Vohrared

2012 年 5 月发布

下载:
下载Oracle JDeveloper 11.1.2
下载Oracle Database 11g 快捷版
下载示例应用程序

简介

JavaServer Faces 2.0 提供了非常适合云环境虚拟化计算资源的特性。这个由两部分组成的文章的第二部分探讨 Ajax 支持、视图参数、预导航、事件处理和可收藏的 URL。

在本文的第 1 部分,我演示了 JavaServer Faces 2.0 (JSF 2.0) 中的一些支持云的新特性:

  • 资源处理
  • @ManagedBean 批注
  • 隐式导航

在第 2 部分,我们将介绍下列特性:

  • Ajax 支持
  • 视图参数
  • 预导航
  • 事件处理
  • 可收藏的 URL

注:您可以从这里下载从第 1 部分就开始使用的示例应用程序的源代码。示例代码的有些部分已经被注释掉,可以针对本文的相关部分取消其注释。

为 JSF 组件添加 Ajax 功能

JSF 2.0 引入了一项新规定,即可以给任何 JSF 组件添加 Ajax 功能。Ajax 可以实现客户端和服务器之间的异步数据传输。JSF 2.0 中的 Ajax 特性包括对局部页面呈现、局部页面处理和组件分组的支持。

可以通过以下任一方法添加 JSF 2.0 Ajax 功能。

  • f:ajax 标记
  • JSF Ajax 库 (jsf.js)

一般优先使用 f:ajax 行为标记。这样可以让您避免在组件标记中编写 JavaScript 代码,从而简化 JSF 开发。不过,必须要理解专用于处理 Ajax 的组件行为 AjaxBehaviorEvent

虽然这两种方法我们都将讨论,但我们将使用 jsf.js Ajax JavaScript 库来演示如何使用 h:outputScript 标记添加 JavaScript 资源。
 

使用 JSF Ajax JavaScript 库

在我们应用程序的 Ajax 版中,我们将使用 Ajax 提交 SQL 查询来创建一个数据表格。input.xhtml Facelets 页面的 Ajax 版包含一个 h:dataTable 元素,其 value 绑定到托管 bean 属性 resultSet,该属性包含一个根据输入域中指定的 SQL 查询返回的 JDBC ResultSet

下面是使用 JSF Ajax 库的方法。

添加一个 h:outputScript 标记,包括库名称和脚本名称 (jsf.js),并将 target 属性指定为 head

target 属性指定了 JavaScript 要呈现在的元素。因为 jsf.js 封装在 META-INF\resources\javax.faces 文件夹中的 jsf-impl.jar 文件中,所以可以将该脚本以 JAR 资源包的形式添加。

 <h:outputScript library="javax.faces" name="jsf.js" target="head"/>  

如果 JavaScript 脚本位于 JAR 文件外,可以通过在 resources 文件夹中为其创建一个库来添加它。例如,如果 jsf.js 脚本单独存在,则可以在 resources 文件夹中创建一个 js 文件夹,将该脚本复制到该文件夹,然后使用 h:outputScript 标记引用该脚本的方式来添加该脚本。

 <h:outputScript library="js" name="jsf.js" target="head"/>  

在 Facelets 页面中添加 h:outputScript 标记属性时可以借助代码提示。添加 h:graphicImage 标记时可以使用 EL 表达式 #{resource['images:logo.jpg']} 指定资源,例如:

 <h:graphicImage value="#{resource['images:logo.jpg']}"/>  

如果在指定路径未找到资源,则生成错误 src="RES_NOT_FOUND",并且不显示图像,这可以通过将资源添加到指定路径来修复。

h:inputText 中调用 jsf.js JavaScript 来发送 Ajax 请求。valueChangeListener 有一个值绑定到托管 bean 中的 commandButton_action 方法。

<h:inputText id="inputText1" onchange="jsf.ajax.request(this, event, {render: 'form1:dataTable1'}); return false;"  
      binding="#{catalog.inputText1}" valueChangeListener="#{catalog.commandButton_action}"
                             required="true"/>

通过 jsf.js 库,使用 jsf.ajax.request 函数将 Ajax 添加到 inputText 组件。在 onchange 事件处理程序中调用 jsf.ajax.request() 函数。该函数有三个参数:

  • 第一个参数是调用该函数的组件,即 this
     
  • 第二个参数是调用事件,即 event
     
  • 第三个参数是一个 JavaScript 对象。该对象有 executerender 两个属性。这些属性指明组件的客户端 ID。客户端 ID 是以该组件所在命名容器中所有父级组件的 ID 为前缀的组件 ID。例如,我们指定 form1.dataTable1 作为 render 属性中的客户端 ID。onchange 事件 return false 表示表单未提交。valueChangeListener 属性指定发起 Ajax 请求时调用的托管 bean 方法。

修改 commandButton_action 方法以返回 null 而非 output,因为使用 Ajax 时,我们将更新发送请求的页面,但不导航到 output.xhtml

 return null; //return "output"; 

可以使用 f:ajax 标记来代替 jsf.js 脚本。f:ajax 标记呈现 dataTable1 数据表格,它绑定到监听器 commandButton_actionAjaxBehaviorEvent 事件),除托管 bean 中的 AjaxBehaviorEvent 参数之外,它与用于 jsf.js 脚本版本的 commandButton_action 方法相同。

<h:inputText binding="#{catalog.inputText1}" required="true">
    <f:ajax execute="@form" event="valueChange" render="dataTable1"
            listener="#{catalog.commandButton_action}"/>
   </h:inputText>

execute 属性指定 JSF 页面的哪些组件(如果指定多个组件,以空格分隔)参与请求的执行部分。

event 属性指定哪个文档对象模型 (DOM) 事件发起 Ajax 请求。指定 valueChange 事件,这也是 JSF EditableValueHolder 组件(如输入文本组件)的默认事件。

render 属性指定在请求的 Render Response 阶段要呈现的组件。由于 dataTable1 组件与 inputText 组件处于同一层级,因此在 render 属性中省略了“form1”,只指定 dataTable1

listener 属性指定 AjaxBehaviorEvent 广播时要调用的方法。在以下子部分中,使用 JSF Ajax JavaScript 库来发送 Ajax 请求。

右键单击 input.xhtml,使用 URL http://localhost:7101/JSF2-ViewController-context-root/faces/input.xhtml 运行 input.xhtml

将呈现图像和 style.css 资源,如图 1 所示。


图 1. 呈现的图像和 style.css 资源

指定一个 SQL 查询 (SELECT * FROM OE.CATALOG),然后在页面中单击输入文本域外的某处来触发 valueChange 事件。将呈现一个数据表格(参见图 2),它表明该页面已使用 <script> 标记引用了 jsf.js


图 2. 呈现的数据表格

也可以在其他元素(如 </a>)中使用 EL 表达式引用图像资源。

 <a href="#{resource['images:logo.jpg']}" >Logo</a>  

重新运行 input.xhtml。将呈现 Logo 链接,如图 3 中所示。


图 3. 呈现的 Logo 链接

单击 Logo 链接。将呈现 logo.jpg 图像,如图 4 所示。


图 4. 呈现的 logo.jpg 图像

我们到目前只使用了最简单的命名约定形式来调用 resources 文件夹中资源(库和资源文件),但资源标识符还可以包括其他组件。资源标识符的完整格式可以包括区域前缀、库版本以及资源版本,这增加了对本地化和版本控制的支持。resourceName 是资源标识符唯一必要的组成部分。

资源重定位的缺陷

有些资源(如 JavaScript 脚本)可以在资源标记位置之外的其他位置呈现。当我们在 h:ouputScript 标记中指定 target 属性来在 <head> 元素中呈现 <script> 元素时我们就使用了资源重定位。

 <h:outputScript library="javax.faces" name="jsf.js" target="head"/>  

如果我们省略了 target 属性,则就地呈现 jsf.jsh:outputStylesheet 只在 <head> 元素中呈现。如果要将脚本呈现在 <head> 元素中或者要呈现样式表,则需要在 Facelets 页面中添加 <h:head> 标记。

可以注释掉 h:head 标记来看看效果。


图 5. 注释掉 h:head 标记

重新部署 Facelets 应用程序并运行 input.xhtml 页面。虽然了 h:graphicImage,但没有调用 style.cssjsf.js,这从所显示页面中缺少样式和 Ajax 特性可以知道,如图 6 所示。


图 6. 缺少样式和 Ajax 功能

视图参数

JSF 2.0 开始支持视图参数,规定可以在 GET 请求中发送请求参数。视图参数是一个由 UIViewParameter 类表示的 UI 组件。就像其他 UI 组件一样,它保存在 Facelets 页面的 UI 组件树中,并可以与验证器和转换器关联。视图参数是一个 EditableValueHolder,因为它实现了这种接口。

视图参数到托管 bean 属性的映射在 f:viewParam 标记中指定,而这些标记则在 f:metadata 标记内指定。f:metadata 标记在 f:view 标记内指定。对于 JSF 2.0 Facelets 来说,f:view 标记是可选的;如果使用该标记,则它应是最外层的组件标记。

例如,将以下 f:viewParam 标记添加到 f:metadata 标记,指定视图参数 catalogIdvalue 绑定到托管 bean 属性 catalogId

<f:view> 
<f:metadata> 
  <f:viewParam id="catalogId" name="catalogId" value="#{catalog.catalogId}"/> 
 </f:metadata>
...
</f:view>

视图参数的绑定是在 Facelets 页元数据中指定的,而视图参数的值则在 GET 请求 URL 中指定。当使用 URI 发送 GET 请求时,按照视图参数映射使用 GET 请求参数值设置托管 bean 属性,例如:

 http://localhost:7101/context-root/faces/input.xhtml?catalogId=2.  

预导航

预导航允许根据导航情景和视图参数确定要导航到的资源文件以及请求参数(如果有的话)。而且,您还可以为从 GET 请求访问的 JSF 资源创建 URL,从而使所显示的 URL 显示资源和所有请求参数。我们将在“REST 式 GET URL 和可收藏性”部分中使用一个示例来对此进行讲解。

预导航是在激活或选择 h:linkh:button 之前确定 h:linkh:button 组件的目标 URL。h:link 组件生成一个 <a/> 标记,h:button 组件生成一个 button 类型的 <input/> 标记。

预导航的优点在于 <a/> 标记或 <input/> 标记的目标 URL 未硬编码在 h:linkh:button 中。例如,您可能希望在目标 URL 中包含可变的请求参数。h:linkh:button 组件的目标 URL 是在 Render Response 阶段预先确定的,并在用户单击以激活该组件时导航到该地址。

导航情景(包括目标 URL 和目标视图 ID)是在用户激活组件之前在 Render Response 阶段评估和解析的,并添加了在单击时生成非 Faces 请求的可收藏 URL 的规定。

预导航可能使用隐式导航或声明式导航来构造目标 URL。当用户激活超链接或按钮时,使用非 Faces 请求得到目标 URL。

JSF 2.0 引入了 UIOutcomeTarget 组件,它有一个与其关联的结果。UIOutcomeTarget 的两个子类是 HtmlOutcomeTargetButtonHtmlOutcomeTargetLink,对应的 UIComponent 标记为 h:buttonh:link。相反,JSF 2.0 之前的 UICommand 组件在 Invoke Application 阶段之后同时解析和实现导航。

PreRenderView 事件处理

JSF 2.0 提供了一个新的系统事件 PreRenderViewEvent。这个事件在处理完视图参数后、呈现视图前分派。该事件的监听器使用 f:event 标记及其 listener 属性来注册。以下是几种应用 preRenderView 事件的方式:

  • 在呈现视图之前加载应用程序数据。
  • 在呈现视图之前设置应用程序上下文。
  • 在分派事件之前,导航到所要呈现视图以外的视图。

例如,在 f:metadata 标记内添加 f:event 标记,并将其 type 指定为 preRenderView,将其 listener 指定为托管 bean 中的 createSQLQuery 方法:

<f:metadata> 
  <f:viewParam id="catalogId" name="catalogId" value="#{catalog.catalogId}"/>	
  </f:viewParam> 
<f:event type="preRenderView" listener="#{catalog.createSQLQuery}" />
 </f:metadata>
 

重新部署并重新运行 input.xhtml,在请求 URL 中包含一个值为 2 的请求参数 catalogIdf:event 标记内的监听器方法 createSQLQuery 在呈现视图之前调用 createSQLQuery 方法来创建和运行一个 SQL 查询以生成用于创建数据表格的结果集。input.xhtml Facelets 页面呈现时会有一个 catalogId 2 对应的数据表格,而无需指定 SQL 查询。


图 7. 使用 PreRenderView 事件生成数据表格

REST 式 GET URL 和可收藏性

在表示状态传输 (REST) 架构中,一个 URI 代表一个资源。例如,可以用 /employee/12345 代表一个员工。

JSF 2.x 支持 REST 式 GET URL。这些 URL 由 h:link 元素生成,且可以被收藏。使用预导航,h:link 组件中指定的结果以及由视图参数处理的请求参数由 JSF 导航处理程序用于在 Render Response 阶段确定目标 URL。产生的目标 URL 是可收藏的。可以使用 includeViewParams 属性在结果目标 URL 中包含视图参数。

例如,向 input.xhtml 添加以下 h:link 元素,从结果 output2 和视图参数(如果有的话)构造目标 URL。

 <h:link outcome="/output2" includeViewParams="true" value="Create DataTable"/>  

对于员工示例,h:link 将如下所示:

 <h:link outcome="/employee/12345" includeViewParams="true" value="Employee 12345"/>  

隐式导航用于将结果映射到一个视图 ID(如前面示例中的 output2.xhtml12345.xhtml),随后,使用预导航在 h:link 元素生成的 GET 请求的 Render Response 阶段期间构造目标 URL。使用 paramname=paramvalue 格式将视图参数添加到目标 URL。还可以在结果本身中指定 includeViewParams

 <h:link outcome="/output2? includeViewParams=true" value="Create DataTable"/>  

当选中(单击)可收藏 URL 时,不会调用 JSF 框架 (Faces servlet),而是生成非 Faces 请求(类似于重定向导航)。

接着,修改 input.xhtml 中的 f:metadata 标记,使之仅包含 f:viewParam 标记,而不含针对 preRenderView 事件的 f:event 标记。

   <f:metadata>
            <f:viewParam id="catalogId" name="catalogId" value="#{catalog.catalogId}"></f:viewParam>
       </f:metadata>

创建一个新的 Facelets 页面 output2.xhtml,用视图参数 catalogId 创建 JSF 数据表格。f:metadata 标记包含一个用于 preRenderView 事件类型的 f:event 标记以及一个监听器 createSQLQuery

<f:metadata>             <f:viewParam id="catalogId" name="catalogId" value="#{catalog.catalogId}"></f:viewParam>             <f:event type="preRenderView" listener="#{catalog.createSQLQuery}"/>         </f:metadata> 

修改托管 bean 中的 createSQLQuery 方法,返回 String 文本“output”,以便导航处理程序使用隐式导航导航至 output.xhtml

 	// return null;     return "output";  

使用包含请求参数的 URL http://localhost:7101/JSF2-ViewController-context-root/faces/input.xhtml?catalogId=2 重新部署和运行 Facelets 页面 input.xhtml。将呈现一个可收藏的超链接(参见图 8)。该链接的目标 URL 在呈现阶段构造。


图 8. 可收藏链接

要找到目标 URL,请选择 View>Source。Create DataTable 链接的目标 URL 包括作为资源页面的 output2.xhtml Facelets 页面以及作为请求参数的 catalogId

从结果值 output2 可确定 output2.xhtml 是使用隐式导航的目标资源。使用视图参数将请求参数 catalogId 添加到 URL。


图 9. 使用预导航构造带目标 URL 的可收藏式链接

单击 Create DataTable 超链接。URL http://localhost:7101/JSF2-ViewController-context-root/faces/output2.xhtml?catalogId=2 在浏览器中调用。

output2.xhtml Facelets 页面配置了一个 preRenderView 事件,它导航到 output.xhtml。我们不必在 h:link 中硬编码目标 URL。根据请求参数值的不同,目标 URL 是可变的。在回发中,将呈现和显示 output.xhtml 中的数据表格。


图 10. 呈现的数据表格

总结

在本文中,我们讨论了一些 JSF 2.0 特性(如 Ajax 支持、视图参数、预导航、事件处理和可收藏 URL),这些都使 JSF 2.0 非常适合云环境。展望未来,Java Platform, Enterprise Edition 7 (Java EE 7) 计划将针对与云相关的实际问题(如多租户和灵活性,也称横向伸缩)更新 JSF 规范。

另请参见

关于作者

Deepak Vohra 是 NuBean 顾问、Web 开发人员、Sun 认证 Java 1.4 程序员以及 Sun 认证的 Java EE Web 组件开发人员,也是 Oracle Database 10g 的 Oracle 初级认证管理员。