站内搜索: 请输入搜索关键词

当前页面: 开发资料首页J2EE 专题集成 Struts、Tiles 和 JavaServer Faces

集成 Struts、Tiles 和 JavaServer Faces

摘要:
将 Struts、Tiles 和 JavaServer Faces (JSF) 一起使用,开发人员可以实现易于管理和重用的、健壮的、界面清晰的 Web 应用程序。

Struts 框架推出已经有一段时间了,它已经成为在开发 J2EE Web 应用程序时开发人员所采用的事实上的标准。Tiles 框架是在 Struts 之后不久出现的,它通过为开发人员提供用组件组装展示页面的能力开拓了自己的生存环境。JSF 是 Web 应用程序框架中最新的成员,它提供了验证用户输入和处理用户事件的机制,最重要的是,这是一种以协议无关的方式呈现用户界面组件的方法(有关这些 技术的概况,参见本文相关页面“The major players”)。

尽管 Struts 和 JSF 中有一些功能是重叠的,但是它们在其他方面起到了互为补充的作用。这三种技术的结合可以为开发 Web 应用程序、组织其展示和以协议无关的方式呈现定制的用户界面(UI)组件提供一种高效的途径。

为了运行本文中的示例代码,需要 Struts 1.1、Tiles、JavaServer Faces Reference Implementation (JSF-RI) Early Access Release 4.0 以及 Struts-Faces 0.4。Jakarta 项目提供的 Struts 1.1 发行版本将 Struts 和 Tiles 捆绑发布。还可以从 Jakarta 项目上下载 Struts-Faces 集成库。JSF-RI 是 Sun 的 Web 开发工具包(Web Services Developer Pack)的一部分(在参考资料中有这些下载和示例代码的链接)。

现在回到集成三种技术的细节上。首先有个坏消息:在本文发表的时候,这三种技术是不能直接互操作的。好消息是:在本文中,我们展示了集成 Struts、Tiles 和 JSF 的方法。我们假设您已经了解 Struts 和 Tiles。对 JSF 有一些了解会有帮助(参阅 参考资料中提供的 developerWorks 上的 JSF 教程的链接),但是不了解也不妨碍对本文的理解。

JSF 简介
JSF 应用程序是使用 JSF 框架的普通 J2EE Web 应用程序,JSF 框架提供了丰富的 GUI 组件模型,这些模型体现了真正的 GUI 框架内涵。您可能听人们说过,尽管某种技术不错,但是它的外观仍然需要改进。是的,用 HTML 组件构建平淡无奇的页面的日子已经过去了,如果使用 JSF 的话,具有更高级 GUI 外观的日子就在眼前。您会问,怎么做呢?树形组件、菜单组件和图形是已经存在的 UI 组件,这些 JSF 一定要提供。更进一步,JSF 通过提供容易使用的 API 鼓励创建自定义组件。

注: 这里所提到的 UI 组件是 Sun 提供的示例的一部分。像所有规范一样,实际的实现由不同的提供商完成。

在传统的使用模型-视图-控制器(MVC)的 Web 应用程序中,GUI 组件是由处理展示和业务逻辑的自定义标记所表示的。这样就出现了必须“编写与客户机设备打交道的代码”的问题,这会产生重复的代码。使用 JSF 就不会有这个问题。

JSF 结构将展示逻辑 (“什么”)与 UI 组件的业务逻辑 (“为什么”和“如何”)分离。通过在 JSP 页面中使用 JSF 标记,就可以将 renderer 与 UI 组件关联在一起。一个 UI 组件可以用不同的 renderer 从而以不同的方式呈现。特定于 UI 组件的代码在服务器上运行,并且响应用户操作所产生的事件。

JSF-RI 提供了一个 render kit,它带有一个自定义标记库,用以从 UI 组件呈现 HTML。它还提供了根据需要定制这些组件外观的能力。如果需要特殊的组件,那么可以为特定的客户机设备构造定制的标记并让它与一个子 UI 组件和定制的 renderer 相关联。对于不同的设备,您所需要做的就是指定不同的 renderer。

JSF 和 UI 组件
您可能已经用 Java AWT 或者 Swing API 创建过 Java GUI 应用程序,所以您应该熟悉 JSF 的 UIComponent (它与 AWT 或者 Swing 组件很相像)。它储存其子组件的树(如果有的话)并为客户端发生的动作生成标准事件,例如单击一个按钮以提交表单。这些事件缓存在 FacesContext 中。您可以用自定义标记关联每一个这种事件的处理程序。例如,用一个自定义的 ActionListener 处理用户单击或者表单提交。

JSF UIComponent、Renderer 和标记总是共同工作的。所有 JSP 自定义标记都是通过继承 UIComponentTag 创建的。doStart 和 doEnd 方法总是在 UIComponentTag 类中实现。您只需在这些标记类中提供其他的功能。

图 1展示了自定义标记、UI 组件和 renderer 之间的关系。客户机浏览器访问用 JSF 标记(jsf:myTag)表示 UI 组件(MyComponent)的 JSP 页面。这个 UI 组件运行在服务器上,并用适当的 renderer (MyRenderer)以 HTML 的形式呈现给客户。这个 JSP 页面表现了在 JSF-RI 中使用带自定义标记的用户界面组件而不是在 HTML 中对它们进行编码。

例如,图 1 展示了 h:panel:group 标记的使用。这个标记用于将一个父组件下面的各个组件组织到一起。如果与像 panel_grid 和 panel_data 这样的其他面板标记共同使用,那么它会在运行时生成 HTML 表中的列的标记。JSF-RI-提供的 html_basic 标记库用于表示像文本字段、按钮这样的 HTML 组件

JSF 生命周期
JSF 生命周期包括六个阶段:一个传入的请求可能会经历全部阶段,也可能不经历任何阶段,这取决于请求的类型、在生命周期中发生的验证和转换错误以及响应的类型。JSF 框架处理由 JSP 页生成的 Faces 请求,并返回 faces 或者 non-faces 响应。

在提交一个 JSF 表单,或者当用户单击指向在 URL 中具有 /faces 前缀的 URL 的链接时,就会出现 faces 响应。所有 faces 请求都由一个 FacesServlet 处理 -- 这是 JSF 中的控制器。

发送给一个 servlet 或者一个没有 JSF 组件的 JSP 页面的请求称为 non-faces 请求。如果结果页中有 JSF 标记,那么它就称为 faces 响应,如果没有 JSF 标记,就是 non-faces 响应。

JSF 生命周期有六个阶段:

重建请求树
应用请求值
进行验证
更新模型值
调用应用程序
呈现响应
根据 JSF 规范,每一阶段表示请求处理生命周期的一个逻辑概念。不过在 JSF-RI 中,这些阶段是由具有对应名字的实际类表示的。下面一节描述了每一阶段是如何对请求进行处理并生成响应的。您将首先看到的是处理一个 faces 请求所涉及的阶段,然后是处理 faces 响应所涉及的阶段。

处理 faces 请求
为了理解 JSF 请求处理,请看 FlightSearch.jsp,这是清单 1中的一个简单的 JSF 表单。一个 JSF 页面基本上就是这个样子的。这个 JSF 表单有输入文本字段 from 和 to cities、departure 和 return dates,还有提交和重设表单的按钮(我们会在稍后分析清单1中每一个标记的意义)。现在,假设提交这个表单产生了一个 faces 请求。

这个请求被 FacesServlet 所接收、并在向客户发回响应之前通过不同的阶段。图 2展示了如何对 JSF 请求进行处理。让我们看一看这是如何进行的。

1. 接收请求
FacesServlet 接收请求并从 FacesContextFactory 得到 FacesContext 的一个实例。

2. 委托生命周期处理
FacesServlet 通过对在 faces 上下文中传递的 Lifecycle 实现调用 execute 方法将生命周期处理委托给 Lifecycle 接口。

3. Lifecycle 执行每一阶段
Lifecycle 实现执行从重建组件树阶段开始的每一阶段。

4. 创建的组件树
在重建组件树阶段,用 travelForm 中的组件创建一个组件树。这个树以 UIForm 作为根,用不同的文本字段和按钮作为其子组件。

fromCity 字段有一个验证规则,它规定其不能为空,如 validate_required 标记所示。这个标记将 fromCity 文本字段与一个 JSF Validator 链接起来。

JSF 有几个内建的验证器。相应的 Validator 是在这个阶段初始化的。这个组件树缓存在 FacesContext 中、并且这个上下文会在后面用于访问树及调用任何一个事件处理程序。同时 UIForm 状态会自动保存。所以,当刷新这一页时,就会显示表单的原始内容。

5. 从树中提取值
在应用请求值阶段,JSF 实现遍历组件树并用 decode 方法从请求中提取值,并在本地设置每一个组件。如果在这个过程中出现了任何错误,那么它们就在 FacesContext 中排队并在呈现响应阶段显示给用户。

同时,在这个阶段排队的所有由像单击按钮这样的用户操作产生的事件,都广播给注册的侦听器。单击 reset 按钮会将文本字段中的值重新设置为它们原来的值。

6. 处理验证
在处理验证阶段,对在应用请求值阶段设置的本地值进行所有与各组件相关的验证。当 JSF 实现对每一个注册的验证器调用 validate 方法时就会进入此阶段。

如果任何一项验证失败,那么生命周期就会进入呈现响应阶段,在那里呈现带有错误信息的同一页面。在这里,所有在这一阶段排队的事件同样都会广播给注册的侦听器。

JSF 实现处理源字段上的验证器。如果数据是无效的,那么控制就交给呈现响应阶段,在这个阶段重新显示 FlightSearch.jsp 并带有相关组件的验证错误。通过在 JSP 页面中声明 output_errors,页面中的所有错误都会显示在页面的底部。

7. 设置模型对象值
在更新模型值阶段,成功处理了所有验证后,JSF 实现就通过对每一组件调用 updateModel 方法用有效值设置模型对象值。如果在将本地数据转换为由模型对象属性所指定的类型时出现任何错误,那么生命周期就进入呈现响应阶段,并将错误显示出来。来自表单字段属性的值会填充为模型对象的属性值。

8. 可以调用 ActionListener
可以将一个 ActionListener 与一个用户操作,如单击提交按钮相关联,如清单 1所示。在调用应用程序阶段,对 FlightSearchActionListener 调用了 processAction 方法。在实际应用中,processAction 方法在调用后会搜索数据以找出满足条件的航班,并从组件的 action 属性中提取输出。

在本文提供的这个示例 Web 应用程序中,我们使用了静态数据表示航班表。这个方法还将提取的 action 属性发送给 NavigationHandler 实现。NavigationHandler 查询 faces-config.xml 文件 -- 这是 JSF 的默认应用程序配置文件 -- 以根据这一输出确定下一页是什么。

9. 呈现响应
在呈现响应阶段,如果在 faces 上下文中没有错误,就显示由查询配置文件得到的这一页 FlightList.jsp。如果是因为前面任一阶段的错误而到达这一阶段的,那么就会重新显示带有错误信息的 FlightSearch.jsp。

清单 1. FlightSearch.jsp,一个简单的 JSF 表单

<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>








valueRef="FlightSearchBean.departureDate">
valueRef="FlightSearchBean.arrivalDate">

label="Submit" commandName="submit" >
type="foo.bar.FlightSearchActionListener"/>

commandName="reset" />







在这段代码中使用了两个 JSF-RI 的标记库。html_basic 标记库定义了 HTML 组件常用的标记,而 jsf-core 标记库包含用于注册侦听器和验证器的标记。其他标记有:

f:use_faces 标记向 JSF 实现表明后面的标记是 faces 标记。


f:validate_required 标记表明它所在的字段(在 FlightSearchBean 中是 fromCity 字段)在提交表单时应该有值。


h:form 和 h:input_text 标记分别表示一个名为 flightSearchForm 的 HTML 表单和各种文本字段。


h:command_button 标记用于表示提交和重设按钮。


最后,h:output_errors 标记类似于 Struts html:errors 标记,用于显示在表单字段验证中出现的任何错误。
一个名为 FlightSearchBean 的 JavaBean 表示在更新模型值阶段用 UIComponent 数据更新的模型。通常在 JSP 页中 JavaBean 是用 jsp:useBean 标记声明的。您可能注意到了在 FlightSearch.jsp 中没有这样做。这是因为可以使用 JSF 的一个名为 Managed Beans 的功能,在 faces 配置文件中声明所有 JSP 页面使用的 JavaBeans 组件。在开始时,servlet 容器会初始化这些 JavaBeans 组件。faces-config.xml 文件中的 FlightSearchBean 入口如清单 2所示:

清单 2. faces-config.xml 的 TravelInfoBean 入口


FlightSearchBean

foo.bar.FlightSearchBean

session




现在让我们看一看这些阶段是如何处理响应的。

呈现 faces 响应
一个 faces 响应是由 Faces 应用程序在生成包含 JSF 标记的 JSP 页时生成的。这个响应可以是 JSF 应用程序的 faces 或者 non-faces 响应。

在我们的例子中,清单 1 中页面的呈现是一个 faces 响应。您可能熟悉 Tag 接口的 doStartTag() 和 doEndTag() 方法。在 JSF 和 Struts-Faces 中,每一个标记都是从 UIComponentTag 扩展的。UIComponentTag 实现了 doStartTag() 和 doEndTag() 方法。

它还有两个抽象方法 getComponentType() 和 getRendererType()。通过在具体的标记类中实现这两个方法,就可以分别指定组件和 renderer 的类型。

考虑一个带有文本字段的简单 JSF 表单。在呈现 JSF 表单时执行以下一系列步骤。

1. 调用 doStartTag() 方法
Servlet 窗口对 FormTag 调用 doStartTag() 方法。

2. 得到 UIComponent
FormTag 从 getComponentType() 方法得到其 UIComponent。UIComponentTag (FormTag 的父组件)使用 getComponentType() 以从 faces-config.xml 文件中查询这个组件的类名,并创建 UIComponent(FormComponent)的一个实例。

3. 得到 renderer
下一步,FormTag 从 getRendererType 方法中得到其 renderer 。与组件类型一样,renderer 名是在 faces-config.xml 文件中查询的。

4. 调用编码方法
在创建了 FormComponent 和 FormRenderer 后,对 FormComponent 调用 encodeBegin() 方法。每一个标记的呈现都由 encodeBegin() 开始、由 encodeEnd() 结束。encodeBegin() 方法是按嵌套的顺序调用的。

5. 结束标记和呈现 HTML
servlet 容器对标记调用 doEndTag() 方法。以嵌套的反顺序对每一个组件调用 encodeEnd() 方法。在最后,表单和所有嵌套的组件都呈现为 HTML。这时,HTML 就生成完毕,并呈现出对应于 JSP 的 HTML。

为什么将这三者集成为一体?
随着 JSP 和相关规范的不断发展,像 JSF 和 JSP 标记库(或者 JSTL,它使用简单的标记封装许多 JSP 应用程序常用的核心功能)这样的新标准正在不断出现。下面是使用集成为一个整体的新技术一些好处:

更清晰地分离行为和展示。 将标记、 renderer 和组件分离,就可以更好地定义开发周期中的页面作者和应用程序开发人员的作用。


改变一个组件的展示不会有雪崩效应。现在您可以容易地只对 renderer 作出改变。在传统的 MVC 模型中,由于没有这种分离,对于标记的任何改变都需要改变业务逻辑。现在再不需要这样了。


renderer 无关性。 也可以说是协议无关性,通过对带有多个 renderer 的多种展示设备重复使用组件逻辑实现。使用不同 renderer 的能力使得不再需要对特定的设备编写整个表示层代码。


组装和重用自定义组件的标准。JSF 的考虑范围超出了“表单和字段”,它提供了丰富的组件模型用以呈现自定义 GUI 组件。用 JSF 可以定制每一个组件在页面中的外观和行为。开发人员还拥有创建他们自己的 GUI 组件(如菜单和树)的能力,这些组件可以用简单的自定义标记容易地加入到任何 JSP 页面中。就像 AWT 和 Swing 所提供的 Java 前端 GUI 组件一样,我们可以在我们的 Web 页而中有自定义的组件,它们使用自己的事件处理程序并有定制的外观。这是 Web 层的 GUI 天堂!
Struts 是一种已经拥有大量客户基础的框架。许多 IT 部门认识到这种 MVC 框架的价值并使用它有一段时间了。JSF 没有像 Structs 这样强大的控制器结构,也没有像它那样标准化的 ActionForm 和 Actions(及它们声明的能力)。将 Tiles 集成到集合体中,就给了自己重复使用和以无缝的方式改变公司布局的能力。

移植支持 JSF 的 Struts 应用程序的挑战是双重的。首先,Struts 标记不是 JSF 兼容的。换句话说,它们没有像 JSF 规范所规定的那样扩展 UIComponentTag,所以,JSF 不能解释它们并关联到 UIComponent 和 Renderers。

其次,在 FacesServlet 与 Struts RequestProcessor 之间没有链接。在 Struts 应用程序中,RequestProcessor 负责用 ActionForm 和 Actions 类中的回调方法显示。ActionForm 属性和 validate() 的 getter 和 setter 是 ActionForm 中的回调方法。对于 Action,execute() 是回调方法。除非调用了 RequestProcessor,否则 Struts ActionForm 和 Actions 类中的回调方法没有机会调用业务逻辑。

将 Struts 和 JSF 与 Struts-Faces 集成
这里,您可能会问是否有软件可以帮助将 Struts 与 JSF 集成,或者是否必须自己编写集成软件。

好消息是已经有这样的软件了。Struts-Faces 是一个早期发布的 Struts JSF 集成库。这个库是由 Craig McClanahan 创建的,它使得将现有 Struts 应用程序移植到 JSF 变得容易了(保留了对现有 Struts 投资的价值)。Struts-Faces 还力图与 JSF 进行简洁的集成,这样就可以在前端使用 JSF,同时后端仍然有熟悉的 Struts 组件。

图 4 展示了 Struts-Faces 与 JSF 类之间的关系。蓝色的类属于 Struts-Faces。

图 4. Struts-Faces 类图
单击这里以查看该图。

下面是 Struts-Faces 的主要组件:

FacesRequestProcessor 类,它处理所有 faces 请求。这个类继承了常规 Struts RequestProcessor,并处理 faces 请求。Non-faces 请求发送给出其父类 -- RequestProcessor。


ActionListenerImpl 类,它处理像提交表单或者单击链接这样的 ActionEvent。这个类用于代替由 JSF-RI 提供的默认 ActionListener 实现。只要在一个 faces 请求中生成 ActionEvent,就会对 ActionListenerImpl 调用 processAction() 方法、并将 ActionEvents 转送给 FacesRequestProcessor。这很有意思,因为 RequestProcessor 通常只由 ActionServlet 调用以处理 HTTP 请求。


FormComponent 类,它扩展了 JSF Form 组件,但是是在 Struts 生命周期内调用的。


FormComponent 的 renderer 和标记。


只用于输出的数据标记和 renderer ,这里不需要分离组件。例如,ErrorsTag 和 ErrorsRenderer 用于在 HTML 中显示表单错误。


ServletContextListener 的名为 LifeCycleListener 的实现。它用于在初始化时注册相应的 RequestProcessor。


faces-config.xml 文件。这个文件已经捆绑在 struts-faces.jar 文件中。
清单 3 展示了使用 Struts-Faces 标记的 FlightSearch.jsp。它类似于在清单 1中展示的 JSF 例子。这里用粗体突出了区别之处。在这里,您会发现增加了一个新标记库 tags-faces。这个标记库定义声明这些标记由 Struts-Faces API 所使用。

清单 3. FlightSearch.jsp 使用 Struts-Faces 标记

<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %>
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %>
<%@ taglib uri="http://jakarta.apache.org/struts/tags-faces"
prefix="s" %>






valueRef="FlightSearchForm.departureDate">
valueRef="FlightSearchForm.arrivalDate">

commandName="submit" />
commandName="reset" />







s:form 标记用于创建这个 HTML 表单。表单的 action 属性是 /listFlights 而不是像清单 1那样指定为表单名 flightForm。在 JSF 中,表单名只是指定给 UIForm 的名字而没有更多的意义。

FlightSearchBean 是 JSF 表单的模型,并在更新模型值阶段得到其值。不过在 Struts 中,表单 action 指向 Struts 配置文件 struts-config.xml 中的 ActionMapping。为了理解它是如何起作用的,还必须看一下清单 4 中显示的 struts-config.xml 文件。

您会看到 /listFlights 的 ActionMapping 表明这个 URI 路径的 ActionForm 是 foo.bar.FlightSearchForm,而 Action 类是 foo.bar.FlightSearchAction。换句话说,ActionForm (FlightSearchForm)本身就是 Struts-Faces 中的 HTML 表单的模型,它的 action 间接地指向这个模型(您可以在清单 3 中看到这一点,那里文本字段标记指向 FlightSearchForm。在普通 Struts 应用程序中这会是)。

清单 4. 在 struts-config.xml 中声明 Action

<form-bean name="FlightSearchForm"