集成 Struts、Tiles 和 JavaServer Faces-JSP教程,Java技巧及代码

内容:jsf 简介为什么将这三者集成为一体?将 struts 和 jsf 和 struts-faces 集成将struts 应用程式移植到 jsf集成 struts-faces 和 tiles 的挑战到目前为止所作的改动参考资料 作者简介对本文的评价相关内容:struts, an open-source mvc implementationstruts and tiles aid component-based developmentui development with javaserver facesdeveloperworks toolbox 订阅 订阅:developerworks 时事通讯将三种技术的功能、灵活性和可管理性集成到一起
srikanth shenoy, j2ee 顾问, objectseek inc.nithin mallya, j2ee 顾问, objectseek inc.2003 年 10 月 25 日
你是否想将 javaserver faces (jsf)的强大前端功能、tiles 的内容格式编排优势和 struts controller 层的灵活性都加入到你的j2ee web 应用程式中?企业级 java 专家 srikanth shenoy 和 nithin mallya 为你展示了怎么将这三者的功能集成到一起。本文演示了怎么在 struts-faces集成库中制定类以使得他们能和 tiles 和 jsf 一同使用,并用一个实际的例子解释了这个过程背后的基本原理及怎么使用新的一组类的细节。
将 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 组件。

图1. 呈现一个 jsf 页面

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。

图 2. 处理一个 jsf 请求 单击这里以观看该图。
清单 1. flightsearch.jsp,一个简单的 jsf 表单
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %><%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %><f:use_faces> <h:form id="flightform" formname="flightform" > <h:input_text id="fromcity" valueref="flightsearchbean.fromcity"> <f:validate_required/> <h:input_text/> <h:input_text id="tocity" valueref="flightsearchbean.tocity"> <h:input_text id="departuredate" valueref="flightsearchbean.departuredate"> <h:input_text id="arrivaldate" valueref="flightsearchbean.arrivaldate"> <h:command_button id="submit" action="success" label="submit" commandname="submit" > <f:action_listener type="foo.bar.flightsearchactionlistener"/> </h:command_button> <h:command_button id="reset" action="reset" label="reset" commandname="reset" /> <h:output_errors/> </h:form></f:use_faces>

在这段代码中使用了两个 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入口
<managed-bean> <managed-bean-name>flightsearchbean</managed-bean-name> <managed-bean-class> foo.bar.flightsearchbean </managed-bean-class> <managed-bean-scope>session</managed-bean-scope></managed-bean>

目前让我们看一看这些阶段是怎么处理响应的。

呈现 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。

图 3 显示构成生成 faces 响应的事件序列。

图 3. 呈现一个 faces 响应 单击这里以查看该图。

为什么将这三者集成为一体?随着 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" %> <f:use_faces> <s:form action="/listflights"> <h:input_text id="fromcity" valueref="flightsearchform.fromcity"/> <h:input_text id="tocity" valueref="flightsearchform.tocity"/> <h:input_text id="departuredate" valueref="flightsearchform.departuredate"> <h:input_text id="arrivaldate" valueref="flightsearchform.arrivaldate"> <h:command_button id="submit" action="success" label="submit" commandname="submit" /> <h:command_button id="reset" action="reset" label="reset" commandname="reset" /> <s:errors/> </s:form></f:use_faces>

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 应用程式中这会是 <html:text property="fromcity"/> )。
清单 4. 在 struts-config.xml 中声明 action
<form-bean name="flightsearchform" type="foo.bar.flightsearchform"/><!– ========== action mapping definition ========================= –><action-mappings><!– list flights action –> <action path="/listflights" type="foo.bar.flightsearchaction" name="flightsearchform" scope="request" input="/faces/flightsearch.jsp"> <forward name="success" path="/faces/flightlist.jsp"/> </action></action-mappings>

你会注意到在 action 属性中缺少熟悉的 .do。这是因为 struts-faces 使用表单 action 本身作为表单名(他还应该和 struts 设置文件中的 actionform 名相匹配)。

集成 struts 和 tiles 的五个步骤以下五步能让 struts 1.1 和 tiles 一起工作: 1. 创建一个 jsp 以表示站点的布局。这是主 jsp,并带有页头、页体和页脚的占位符。分别用 tiles 标记添加到主 jsp 页面中。 2. 创建一个 tiles 定义文件并定义每个集成页面的每个占位符中必须包括哪个 jsp 页面。用惟一的名称标识出每一个合成页面定义。 3. 在 struts-config.xml 文件中改动全局和本地转发以使用上一步骤中给出的惟一名称而不是别名。 4. 在启动时用 tilesplugin 装载 tiles 定义文件。将 tilesplugin 项加入到 struts-config.xml 文件中。 5. 将 tilesrequestprocessor 项添加到 struts-config.xml 文件中。这是支持 tiles 的 struts 应用程式的默认请求处理程式。

还要注意我们在这里没有使用 jsf validation 标记。这是因为在 struts 中,验证是在 actionform 类中的 validate() 方法中进行的,有可能是通过使用 commons-validator。 s:errors 标记类似于 struts 错误标记并用于显示在验证时出现的错误消息。

另一件要注意的事情是没有 actionlistener 显式地和提交按钮相关联。这是因为在 struts-faces 中已提供了 actionlistener 并且总是将 faces 请求和 actionevent s 一同转交给 facesrequestprocessor ,在那里根据 struts-config.xml 文件将请求分派给相应的 action 类。

将struts 应用程式移植到 jsf为了将 struts web 应用程式和 jsf 集成,遵循以下步骤:
将 struts-faces.jar 文件和特定于 jsf 的 jar(jsf-api.jar、jsf-ri.jar) 添加到 web 应用程式的 web-inf/lib目录中。 如果准备使用 jsf 和 jstl,则将特定于 jstl 的 jar(jstl.jar、standard.jar)添加到 web-inf/lib 目录中。这一步只有在部署到常规 tomcat 时才会需要。jwsdp 已提供了这些 jar。 修改 web 应用程式部署描述符 ( /web-inf/web.xml)以便有一个 faces servlet 项, 如清单 5 所示。 修改 jsp 页面以使用 jsf 和 struts-faces 标记而不是 struts 标记。特别是用 struts-faces 相应标记替换 html、b ase、 form 和 errors 标记。用 jsf 相应标记替换 text 、 textarea 和 radio 标记。struts-faces 没有独立针对这些的标记。尽管没有需求,不过你可能还会考虑用 jstl 标记替换 struts logic 标记。 对于每一个使用 jsf 标记的 jsp,修改 struts-config.xml 文件以在指向该 jsp 的 action mapping 中的 global-forwards和 local-forwards中加入前缀 /faces。 如果 web 应用程式使用了所有你创建的自定义组件,那么你就需要用 jsf 实现的默认 renderkit 注册他们。能通过在 web-inf 文件中创建一个 faces-config.xml 文件、并增加每一个组件和 renderer 的项做到这一点。不过,要记住 faces-config.xml 文件已绑定在 struts-faces.jar 文件中了。你必须从 struts-faces.jar 文件中提出他、加入自己的内容并将他放到 web-inf目录中。 清单 5. 在 web.xml 中声明 facesservlet
<!– javaserver faces servlet configuration –><servlet><servlet-name>faces</servlet-name><servlet-class>javax.faces.webapp.facesservlet</servlet-class><load-on-startup>1</load-on-startup></servlet><!– javaserver faces servlet mapping –><servlet-mapping> <servlet-name>faces</servlet-name> <url-pattern>/faces/*</url-pattern></servlet-mapping>

集成 struts-faces 和 tiles 的挑战struts-faces 库提供了 struts 和 jsf 之间的一个高效的桥梁,使得在 j2ee web 应用程式中拥有丰富的表示层成为现实。你能通过在组合体中添加 titles 使表示层更丰富,这样不仅得到了 struts 和 jsf 组合的好处,而且还能高效地重复使用不同的 jsp 页面,因为他们将由能根据需要添加或删除的组件部分或 tiles 所构成。

本文已展示了 struts 和 jsp 的集成,你会想将 tiles 加入到组合中只是小事一桩,是不是?

不幸的是,jsf 仍然处于早期阶段,还没有给出最后的发布。基于这一考虑,struts-faces 集成软件研发仍然在不断地发展以包括 jsf 的不同的功能,并且还没有支持 tiles。

struts 和 tiles 能无缝地一起工作,不过在集成之路上你会遇见路障。在下面几小节中,你会看到在和 tiles 一起使用 struts-faces 集成库时经常遇见的问题的汇总。对于每一个问题,我们周详说明了一个修改 struts-faces 类的解决方案。我们将用一个航班搜索示例解释这个解决方案。

清单 6 展示了航班搜索页面的布局。注意我们称他为航班搜索页面而不是 flightsearch.jsp。这是因为 flightsearch jsp 是用户在 foobar 旅行 web 站点看到的合成页面的主体。

目前,我们保持实际的 flightsearch.jsp 不变。我们将随着进展改动他。在你这边,也需要用航班搜索页的定义创建一个 tiles 定义文件。清单 7(紧接着清单 6)展示了 tiles 定义文件中航班搜索页的一项。注意对带有 extends 属性的主布局模板的重复使用。

在清单 6 和 7 后是每一个可能的挑战。
清单 6. 航班搜索例子的 tiles 布局
<%@ taglib uri="/web-inf/struts-tiles.tld" prefix="tiles" %><%@ taglib uri="http://jakarta.apache.org/struts/tags-faces"prefix="s" %><!– layout component parameters: header, menu, body, footer –><s:html><head> <title> <tiles:getasstring name="title"/></title> <s:base/></head><body> <table border="0" width="100%" cellspacing="5"> <tr> <td><tiles:insert attribute="header"/></td> </tr> <tr> <td><tiles:insert attribute="body"/></td> </tr> <tr><td><hr></td></tr> <tr> <td><tiles:insert attribute="footer" /></td> </tr> </table></body></s:html>
清单 7. 航班搜索页的 tiles 定义
<!– master layout definition –><definition name="foobar.master-layout" path="/faces/layout/masterlayout.jsp"> <put name="title" value="welcome to foo bar travels" /> <put name="header" value="/faces/common/header.jsp" /> <put name="footer" value="/faces/common/footer.jsp" /> <put name="body" value="" /></definition> <!– definition for flight search page –><definition name="/foobar.flight-search" extends="foobar.master-layout"> <put name="body" value="/faces/flightsearch.jsp" /></definition>

响应已提交这是你在试图访问航班搜索表单时马上会看到的第一个问题。小心查看堆栈跟踪。你会看到问题出在类 com.sun.faces.lifecycle.viewhandlerimpl 上。这是个实现了 viewhandler 接口的 jsf-ri 类。

图 2展示了 viewhandler 所扮演的角色。这是个将请求转发给下一页的类。在转发请求时,他不在转发前检查响应的状态 — 这只有在使用 tiles 时才会发生,因为 tiles 内部将 jsp 页面包括在响应内,而 jsf-ri 在第一次转发后提交响应、然后试图再次转发给下面的包括 jsp 的 tiles。

要解决这个问题,必须创建一个自定义的 viewhandler 实现,他将检查响应的状态以确定他是否提交过。如果响应没有提交过,那么请求就转发给下一页,否则,就加入请求并显示相应的 jsp。我们将创建一个名为 stfviewhandlerimpl 的类,他实现了 viewhandler 接口并实现了所需要的方法 renderview()。 清单 8 展示了 stfviewhandlerimpl 中的 renderview() 方法:
清单 8. stfviewhandlerimpl 中的 renderview()方法
requestdispatcher rd = null;tree tree = context.gettree();string requesturi = context.gettree().gettreeid();rd = request.getrequestdispatcher(requesturi);/** if the response is committed, include the resource **/if( !response.iscommitted() ) { rd.forward(request, context.getservletresponse());}else { rd.include(request, context.getservletresponse());}

目前你实现了自己的 viewhandler ,怎么通知 jsf-ri 使用你的 viewhandler 而不是默认的实现呢?要回答这个问题,就必须理解 facesservlet 的工作过程。

在 faces 初始化过程中, facesservlet 会让 lifecyclefactory 实现返回 lifecycle 类的一个实现,如清单 9 所示:
清单 9. facesservlet 中 faces 的初始化
//get the lifecyclefactory from the factory finderlifecyclefactory factory = (lifecyclefactory) factoryfinder.getfactory("javax.faces.lifecycle.lifecyclefactory");//get the context param from web.xmlstring lifecycleid = getservletcontext().getinitparameter("javax.faces.lifecycle.lifecycle_id");//get the lifecycle implementationlifecycle lifecycle = factory.getlifecycle(lifecycleid);

lifecycle 实现对象拥有在呈现响应阶段要使用的 viewhandler 。你能通过对 lifecycle 实现调用 setviewhandler 方法让自己的 viewhandler 实现成为默认的。

目前问题变为怎么得到默认 lifecycle 实现?回答是不必这样做。只要创建一个新的实现并用一个惟一 id 注册他,如清单 10 所示:
清单 10. 注册自定义 viewhandler 和 lifecycle
//get the lifecyclefactory from the factory finderlifecyclefactory factory = (lifecyclefactory) factoryfinder.getfactory("javax.faces.lifecycle.lifecyclefactory");//create a new instance of lifecycle implementation – //com.sun.faces.lifecycle.lifecycleimpl//according to the documentation, factory.getlifecycle("stflifecycle") //should work, but jsf-ri has a defect.//hence this workaround of creating a ri class explicitly.lifecycleimpl stflifecycleimpl = new lifecycleimpl();//create a new instance of our stfviewhandler and set it on the lifecyclestflifecycleimpl.setviewhandler(new stfviewhandlerimpl());//register the new lifecycle with the factory with a unique //name "stflifecycle"factory.addlifecycle("stflifecycle", stflifecycleimpl);

你能看到 lifecycleid 硬编码为 stflifecycle 。实际上不是这样。当你回过头分析 清单 9时就会清晰。 facesservlet 从在 web.xml 文件中声明的上下文参数中得到名为 javax.faces.lifecycle.lifecycle_id 的 lifecycle id,如下所示:

<context-param> <param-name>javax.faces.lifecycle.lifecycle_id</param-name> <param-value>stflifecycle</param-value> </context-param>

因为 facesservlet 取决于其初始化时的 lifecycle 实现,在 清单 10中展示的代码应该在 facesservlet 初始化之前执行。通过创建另一个 servlet 并在 facesservlet 之前初始化他而做到这一点。

不过一种更聪明的办法是实现一个 servletcontextlistener 接口。这个类声明两个方法: contextinitialized() 和 contextdestroyed() ,在 web 应用程式被创建及 web 应用程式被销毁之前会分别调用他们。因而 清单 10中的代码在 contextinitialized() 方法中执行,而自定义 viewhandler 已用标识名 stflifecycle 注册到 lifecycle ,并且可被 facesservlet 使用。 servletcontextlistener 类本身是在 web.xml 文件中声明的,如下所示:

<listener> <listener-class>foo.bar.stf.application.stfcontextlistener </listener-class></listener>

这不是注册一个带有自定义 viewhandler 的 lifecycle 惟一方法。事实上 factoryfinder 实现了自己的发现算法以发现 factory 对象,包括 lifecyclefactory 。这些机制按照顺序包括在系统属性中查看工厂实现类名的机制、faces.properties file、或 1.3 services 发现机制( meta-inf/services/{factory-class-name} )。不过,我们讨论的这种机制是最容易的,也是最不具有破坏性的一种。

404 resource not found在解决了提交响应的问题后,单击所有一个 tiles 特定的链接或输入一个会呈现 faces 响应的 url。在这里,能输入显示 flightsearchform 的 url。

在这样做了以后,你会得到一个 foobar.flight-search – 404 resource not found 错误。 foobar.flight-search 是航班搜索页面的 tiles 定义的名字。 facesrequestprocessor 不能处理 tiles 请求(因为他扩展的是 requestprocessor 而不是 tilesrequestprocessor ),所以会得到错误。

为解决这个问题,我们将创建一个名为 stfrequestprocessor (表示 struts-tiles-faces request processor)的新的请求处理程式。目前我们将拷贝 facesrequestprocessor 的所有代码到这个新类中。惟一的差别是 stfrequestprocessor 继承的是 tilesrequestprocessor 而不是继承常规的 requestprocessor 。这个新的 requestprocessor 能处理 tiles 请求。清单 11 周详列出了这个 stfrequestprocessor :
清单 11. stfrequestprocessor.java
正如你所知道的, struts 框架的 requestprocessor 是在 struts-config.xml 文件中指定的。将下面的项添加到 struts-cinfig.xml 文件中后, stfrequestprocessor 就成为处理程式:

<controller processorclass="foobar.stf.application.stfrequestprocessor" />

表单提交显示返回同一个表单由于 stfrequestprocessor 的作用,这时你就能浏览并查看航班页面了。不过,在提交航班搜索表单时,你会得到返回来的同一个表单,而且没有页头和页脚!并且没有验证错误。事实上,根本就没有进行验证!

为了了解到底发生了什么事情,我们用浏览器回到航班页面并检查 html 原始码。你会看到像下面这样的一项:

<form name="flightsearchform" method="post" action="/flightapp/faces/flightsearch.jsp">

注意表单 action 是指向 jsp 页而不是个 .do 的。啊哈!这就是问题!这不是由于同时使用 tiles 和 struts-faces 而带来的新问题,struts-faces 的默认行为是让 jsp 和表单 action 有同样的名字。这种行为在有单一的 jsp 页(如在前面的 struts-faces 例子中)时没有问题。 清单 3展示了原来的 flightsearch.jsp,让我们继续并像下面这样修改 action:

<s:form action="/listflights.do>

当然,光有这种修改并不能解决问题。作了这种改动后,你就会发现 stfrequestprocessor 不能找到 actionform 。显然还需要其他的改动。

不过,在继续往下之前,看一下图&#160 5。他显示了在呈现负责 struts-faces 表单的 faces 时相关的一系列事件。这和 图 3相同,除了在 formcomponent 中突出显示的方法 createactionform()。 由 struts-faces api 提供的 formcomponent 类是 javax.faces.component.uiform 的特别子类,他支持请求或会话范围的表单 bean。

图 5. 呈现 struts-faces 响应 单击这里以查看该图。

正如你所看到的, createactionform() 方法使用 action 名以从 struts 设置文件中得到 actionmapping 。因为没有对于 /listflights.do 的 actionmapping ,所以 struts 不能找到 actionform。

这个问题的解决方法是使用 org.apache.struts.util.requestutils 。 requestutils 中的 static 方法 getactionmappingname() 具有足够的智能解析映射到正确 actionmapping 的路径( /x/y/z)或后缀( .do)。

清单 12 以粗体显示对 createactionform 方法的改动。我们没有对 struts-faces 中的 formcomponent 作这些改动,而是通过继承 formcomponent 并覆盖 createactionform() 方法创建了一个新的 stfformcomponent。
清单 12. formcomponent 中修改过的 createactionform() 方法
// look up the application module configuration information we needmoduleconfig moduleconfig = lookupmoduleconfig(context);// look up the actionconfig we are processingstring action = getaction();string mappingname = requestutils.getactionmappingname(action);actionconfig actionconfig = moduleconfig.findactionconfig(mappingname);……..

对新的 stfformcomponent 还要作一项改动。struts-faces 将 action 名本身作为表单名。这需要改动,因为 action 带有后缀 .do,而表单名没有后缀 .do。所以我们在 stfformcomponent 上增加一个名为 action 的新属性,并覆盖 getaction() 和 setaction() 方法。

formrenderer 的改动必须对 formrenderer (以 html 格式呈现 struts-faces 表单的类)的 encodebegin 方法进行类似于 清单 10所示的修改。

同样,通过继承 formrenderer 做到这一点。此外,还必须改动写出到 html 的表单 action。清单 13以粗体周详列出了这些改动:
清单 13. formrenderer 的改动
protected string action(facescontext context, uicomponent component) { string treeid = context.gettree().gettreeid(); stringbuffer sb = new stringbuffer (context.getexternalcontext().getrequestcontextpath()); sb.append("/faces"); // sb.append(treeid); — this is old code, replaced with // the two lines below. stfformcomponent fcomponent = (stfformcomponent) component; sb.append(fcomponent.getaction()); return (context.getexternalcontext().encodeurl(sb.tostring()));}

formtag的改动 正如你已知道的,当组件和 renderer 改动时,标记也必须改动。在这里,通过继承 struts-faces 中的 formtag 创建一个新的标记: stfformtag 。不必改动所有功能,只要覆盖 getcomponenttype() 和 getrenderertype() 方法。清单 14 展示了从 stfformcomponent 覆盖的方法:
清单 14. formtag 的改动
public string getcomponenttype(){ return ("stfformcomponent");}public string getrenderertype(){ return ("stfformrenderer");}

修改 faces-config.xml 文件自定义组件和 renderer 必须在 faces-config.xml 文件中声明,这样 jsf 框架才能初始化并使用他们。目前我们已创建了一个新组件 stfformcomponent 和一个新 renderer stfformrenderer 。

目前我们将在 faces-config.xml 文件中增加一个声明,如清单 15 所示。 component-class 是组件的完全限定类名。 component-type 指的是在 stfformtag ( 清单 12)中用于标识组件的名字。以类似的方式发现和解释 renderer。注意 faces-config.xml 文件是在 struts-faces.jar 文件中的。从 struts-faces.jar 文件中取出这个文件并将他放到 web 应用程式的 web-inf目录中并修改他。
清单 15. 在 faces-config.xml 中声明自定义组件和 renderer
<faces-config> <!– custom components –> <component> <component-type>stfformcomponent</component-type> <component-class> foobar.stf.component.stfformcomponent </component-class> </component> .. .. .. <!– custom renderers –> <render-kit> <renderer> <renderer-type>stfformrenderer</renderer-type> <renderer-class> foobar.stf.renderer.stfformrenderer </renderer-class> </renderer> .. .. .. </render-kit></faces-config>

修改 struts-faces.tld 文件你不会在这个示例 struts-faces 应用程式中看到 struts-faces.tld 文件,他打包到了 struts-faces.jar 文件中。打开并分析这个文件。他声明了一个名为 org.apache.struts.faces.taglib.lifecyclelistener 的类,这个类实现了 servletcontextlistener 并初始化 facesrequestprocessor 。

因为希望使用新的 stfrequestproccessor ,所以必须将这个文件从 struts-faces.jar 文件中删除,将他放到 web 应用程式的 web-inf 目录中,并删除侦听器声明。如果让这个 tld 文件保持原样,那么在初始化这个 web 应用程式时,除了 stfrequestprocessor ,还会实例化一个 facesrequestprocessor。

修改 base href 标记 目前,你已完成了 struts、tiles、jsf 集成的最困难的部分了。你甚至能浏览航班搜索页面,并输入搜索标准查看航班列表。目前试着从航班列表页面返回航班搜索表单。你会得到一个 http 400 错误。这个错误的原因是 html base href 标记。他被设置为 master layout 页面。

<base href= "http://localhost:8080/stf-example/faces/layout/masterlayout.jsp" /> |_________| |_____________________| context servlet path

程式所有页面浏览都是相对于布局页面计算的。如果加入的 base href 标记只达到 web 应用程式上下文则会非常方便,像这样:

<base href="http://localhost:8080/stf-example/" />

我们能通过制定 struts-faces basetag 做到这一点。这个类中的改动相当微不足道。只须在 base href 中去掉 httpservletrequest.getservletpath() 。

因为这些改动是和显示相关的,所以为他创建了一个名为 stfbaserenderer 的新 renderer。这个新标记称为 stfbasetag ,他声明 stfbaserenderer 作为其关联的 renderer。不必新的组件。

有了这些信息,通过继承 basetag 并覆盖 getrenderertype 方法创建新的 stfbasetag ,如下所示:

public string getrenderertype(){ return ("stfbaserenderer");}

到目前为止所作的改动恭喜!经过这些相对较小的修改,你已成功地集成 struts、tiles 和 jsf,并保留了你以前在这些技术上所做的所有投资。本文演示了怎么将 jsf 强大的前端能力、 tiles 的内容格式编排优势及 struts 控制器层的灵活性结合在一个包中,使得创建一个 j2ee web 应用程式成为一项更容易的任务。

我们讨论了制定 struts 类以便和 javaserver faces 和 tiles 框架形成紧密集成的工作关系,包括下面这些修改和增加:
新的 viewhandler ,用于检查提交的响应。 新的 servletcontextlistener ,用于创建新的 lifecycle 实现并注册这个制定的 viewhandler。 新的 requestprocessor ,用于处理 tiles 请求。 修改过的 web.xml 文件,声明新的 servletcontextlistener 和 jsf lifecycle id。 新的 formtag、 formcomponent 和 formrenderer 类。 新的 basetag 和 baserenderer 类。 修改过的 faces-config.xml 文件,他声明了新的组件和 renderer。 修改过的 struts-faces.tld 文件,不声明侦听器。
希望他能概括本文中使用的复合技术,最重要的是,我们为你提供了将 struts、tiles 和 javaserver faces 结合到用于构建 web 应用程式的一个强大而灵活的机制中的一个令人信服的路线图。

参考资料
你能参阅本文在 developerworks 全球站点上的 英文原文. 下载本文的 例子和代码,并遵循 readme.txt 中给出的编译和部署的说明。 ant 用于对例子进行编译,能从 apache ant 项目web 站点下载他。 有关 struts 和 tiles 的更多内容,包括可下载的教程、文件、二进制文件和原始码,可从 apache jakarta project strutsweb 站点获得。 能将 jsf early acess release 4 (ea4) 作为 java web services developer pack version 1.2 的一部分下载 — 他带有自己版本的 tomcat。 能从 jakarta 站点下载 struts-faces integration library的 0.3 和 0.4 版本。 能从 java web services developer pack 1.2 下载 jsf-ri。 “ struts, an open-source mvc implementation”( developerworks, 2001年2月)介绍了 struts,这是个使用了 servlets 和 javaserver pages 技术的模型-视图-控制器实现。 “ struts and tiles aid component-based development”( developerworks,2002年6月)解释了为什么结合 struts 和 tiles 能成为创建 web 应用程式的出色软件包,并展示了怎么使用他,侧重于 struts 0.9 之后的改动。 “ struttin your stuff with websphere studio application developer, part 2: tiles” ( developerworks,2002年11月)是个教程,主要关注在使用 webshpere studio application developer 作为研发环境时结合 struts 使用 tiles 模型框架。 “ architect struts applications for web services”( developerworks,2003年4月)展示了怎么基于 mvb 设计模式用 struts 建立 web 服务应用程式。 “ a jstl primer”( developerworks,2003年2-5月),这是个分为四部分的系列,提供了有关 jstl 的所有内容,包括怎么使用 jstl 标记以避免在 jsp 页面中使用脚本元素、怎么通过删除表示层中的代码简化软件维护、及 jstl 的简化的表达式语言,他使得无需使用全功能的编程语言就能为 jstl action 指定动态属性值。 学习用 jsf 研发 web 应用程式的基本内容。在其教程“ ui development with javaserver faces” ( developerworks,2003年9月)中,jackwind li guojie 探讨了 jsf 生命周期、输入验证、事件处理、页面浏览和国际化。 sun 的 jsf web 站点是另一个非常好的学习有关 javaserver faces 技术的起点。 serverside.com j2ee 社区是查找有关 j2ee 的资源及参加研发者论坛的最佳地点。 在 java community process站点能迅速得到有关 javaserver pages 1.2 规范的内容。 在 developerworks java 技术专区 能找到关于 java 编程各方面的数百篇文章。

作者简介srikanth shenoy 专门从事大型 j2ee 和 eai 项目的体系结构、设计、研发和部署工作。srikanth 已帮他的制造业、物流业和金融业客户实现了 java 平台“一次编写,随处运行”的梦想。他是 sun 认证的企业架构师,并且是即将出版的 practical guide to j2ee web projects一书的作者之一。能通过 srikanth@srikanth.org和他联系。
nithin mallya 专门为金融客户提供企业级解决方案。他有七年架构和研发服务器端解决方案的经验,大多数是 java 平台。他是 sun 认证的企业架构师,并且是 sun 认证的 web 组件研发者。他也是即将出版的 practical guide to j2ee web projects一书的作者之一。能通过 nithin@mallya.org 和他联系。

  • 以上内容由 华夏名网 搜集整理,如转载请注明原文出处,并保留这一部分内容。

    “华夏名网” http://www.sudu.cn 和 http://www.bigwww.com 是成都飞数科技有限公司的网络服务品牌,专业经营虚拟主机,域名注册,VPS,服务器租用业务。公司创建于2002年,经过6年的高速发展,“华夏名网”已经成为我国一家知名的互联网服务提供商,被国外权威机构webhosting.info评价为25大IDC服务商之一。

原创文章,作者:优速盾-小U,如若转载,请注明出处:https://www.cdnb.net/bbs/archives/31965

(0)
上一篇 2024年7月11日 02:26
下一篇 2024年7月11日

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注

优速盾注册领取大礼包www.cdnb.net
/sitemap.xml