快人一步
VMware 提供培训和认证,助你加速进步。
了解更多Spring Web Flow,很像 Spring Framework 本身,是一项独特的集成技术。我们的大多数用户将其视为一个通用的 应用控制器(ApplicationController),可以嵌入任何环境。我们支持基于 Servlet 和 Portlet 的应用,并提供与主流 Web 框架 Struts、Spring MVC 和 Java Server Faces 的集成。我甚至知道有些团队在 Flex 环境中使用 Spring Web Flow。在这些环境中,Spring Web Flow 都提供了更好的模型来实现在线业务流程,并管理应用状态。
我们的用户喜欢这一点,因为他们可以编写一次控制流程并在任何地方重用。在这个 Web 框架来来去去的时代,Spring Web Flow 为他们提供了一个现代框架来学习和围绕它构建知识、工具和扩展。它从一开始就被设计来扮演这个角色,我很高兴看到 Web Flow 的集成在多个层面不断发展。
我们的集成正在发展的一个重要领域是 Java Server Faces (JSF) 社区。从 Spring Web Flow 1.0.3 开始,我们的 JSF 集成达到了 Spring 社区所期望的水平,并提供了 JSF 开发者在实际工作中最需要的功能。这篇博客将阐述集成增强功能,向您展示 Spring Web Flow 为 JSF 开发者带来的不同之处。
Spring Web Flow 是一个控制器框架,它提供了一种语言和运行时环境,用于在 Web 应用中实现用户界面控制流程。Java Server Faces 是一个用户界面组件框架,由一个标准 API 和两个实现组成——Sun 参考实现和 Apache MyFaces。作为由 JCP 支持的标准 API 规范,JSF 提供了扩展点,允许产品供应商接入并竞争他们的扩展。当用作 JSF 扩展时,Spring Web Flow 承担了两个责任:处理你的视图导航规则和管理与你正在进行的用户交互(即会话)相关的状态。这种集成结合了 Web Flow 在导航和状态管理方面的优势,以及 JSF 作为不断发展的用户界面组件库生态系统的优势。所有 JSF 组件和视图在引入 Web Flow 后仍然像以前一样工作。有了 Web Flow,JSF 开发者受益于一个强大得多的导航模型,它减轻了传统上与手动管理会话状态相关的麻烦。
我可以滔滔不绝地谈论 Web Flow 的具体功能集。但我不会这样做,而是尝试突出那些对 JSF 开发者影响最大的功能。
在导航处理领域,Web Flow 提供
基本上,Web Flow 解决了 这位可怜的开发者 在使用 JSF 基本导航功能时遇到的所有问题。正如 我们的一位主要用户所指出,Web Flow 可以完全替代 JSF 默认的“前向中心”导航模型。
Jeremy Grelle:我一直在使用 SWF 完全替代 JSF 的导航规则,即使对于我们更简单的页面和菜单也是如此。我很高兴能做到这种极端程度,因为在多个地方定义导航规则会让我的团队感到有些困惑。
在状态管理领域,Web Flow 引入了几个会话作用域,作为对 JSF 现有请求、会话和应用作用域的补充。这些作用域包括
回到我们主要用户所说的话
Jeremy Grelle:我发现即使对于 JSF 中更简单的页面,你通常也需要一些东西来管理模型在多次页面请求中的状态......JSF 社区有许多解决此问题的方法(Tomahawk 的 saveState 标签、Shale 的对话框框架等),但我个人认为 SWF 的多作用域是最健壮和优雅的解决方案。
希望到现在你已经对 Web Flow 用作 JSF 扩展时带来的好处有了很好的了解。现在我想更详细地介绍一下集成如何工作,然后以展示如何开始一起使用 Web Flow 和 JSF 来结束。
理解 Web Flow 如何接入 JSF 首先需要理解 Web Flow 的基本构造。Spring Web Flow 中控制器功能的单元——例如账户注册向导或客户主/明细编辑器——被称为一个 流程定义(flow definition)。此类控制器的运行时实例被称为一个 流程执行(flow execution)。在运行时会启动一个新的流程执行,允许单个用户参与与应用的对话。流程执行负责选择用户的初始视图,然后响应用户事件以执行应用行为并确定要显示的下一个视图。它还管理与用户对话相关的状态。
将 Web Flow 与 JSF 集成的一个重要部分是将 Web Flow 的流程执行生命周期融入 JSF 生命周期。这部分是通过实现一个 自定义 PhaseListener 来实现的,该监听器负责处理客户端请求时启动新的流程执行,以及在 JSF 视图恢复期间恢复现有的流程执行。
例如,通过访问如下 URL:
http://localhost:8080/accounts/servlet.faces?_flowId=register-account-flow
... 你将启动一个新的“注册账户流程”(register-account-flow)。请求首先到达 FacesServlet,它会启动 JSF 生命周期的进程。在任何视图恢复之前,FlowPhaseListener 会启动“register-account-flow”,最终决定要显示的初始 JSF 视图。
一旦选择了初始 JSF 视图,它就会被渲染。渲染通常发生在 Spring Web Flow 默认的 暂停时始终重定向 设置后的自动重定向之后,该设置将选定的视图与一个 URL 关联起来,该 URL 可以在以后安全地刷新和导航回退而不会出现浏览器警告。
当渲染发生时,JSF UI 组件可以完全访问在 Web Flow 的任何会话作用域中管理的 Bean,也可以访问你标准的 JSF 会话和应用作用域。组件值绑定是透明的,这意味着 JSF 视图开发者无需知道 Bean 在哪个作用域中管理,他们只需要知道 Bean 的名称。这一能力是通过实现另一个 JSF 扩展点,即 自定义 VariableResolver 来实现的。
在参与流程执行的视图渲染完成后,用户决定她想做什么。如果她决定点击浏览器刷新按钮,那没问题——流程执行会在其稳定的 URL 上刷新,并且相同的 JSF 视图会被重新渲染。如果她决定做一些从同一页面触发 Ajax 请求的事情,那也没问题——流程执行会再次自动恢复,参与 Ajax 调用的 JSF 组件可以无缝地、线程安全地更新流程管理的状态(无双关之意)。
一旦用户决定调用 UI 命令,例如点击命令链接,标准的 JSF 回发生命周期就会启动。处理完 UI 组件验证并更新模型值后,JSF 动作结果会作为事件信号发送给恢复的流程执行的视图状态。流程从那里接管,通过调用相应的应用行为并根据流程定义的导航规则选择下一个视图来处理事件。这个 Web Flow 导航步骤由 JSF 集成中的最后一个关键构造来处理,即 自定义 NavigationHandler。
总而言之,从 JSF 视图和组件开发者的角度来看,这只是标准的 JSF——无论你是使用 JSP 还是 Facelets 以及 XYZ 组件提供商,都无关紧要。Web Flow 执行会自动为你恢复,并且对会话状态的访问是完全透明的。Web Flow 事件只是标准的 JSF UI 命令结果。从控制器开发者的角度来看,一切都是 Web Flow。因此,faces-config.xml 中冗长且有限的导航规则被基于更丰富的流程定义语言的模块化、简洁的流程定义所取代。底线是,你获得了原生 JSF 组件模型的所有优势以及 Web Flow 导航模型的所有强大功能。
在 JSF 环境中开始使用 Spring Web Flow 的最佳方式是 下载 1.0.3 版本 并部署 sellitem-jsf 示例,该示例也可 在线测试。该示例可以作为 Eclipse 动态 Web 项目导入,方便在 Eclipse 中部署。此外,该示例已启用 Spring IDE 2.0,这是一个包含 图形化 Web Flow 编辑器 的 Eclipse 插件。
我将使用 sellitem-jsf 示例简要介绍在 JSF 环境中开始使用 Web Flow 的方法,以此结束本文。
要接入 Web Flow,必须将上面讨论的自定义集成文件添加到 faces-config.xml 中
<application>
<navigation-handler>org.springframework.webflow.executor.jsf.FlowNavigationHandler</navigation-handler>
<variable-resolver>org.springframework.webflow.executor.jsf.DelegatingFlowVariableResolver</variable-resolver>
</application>
<lifecycle>
<phase-listener>org.springframework.webflow.executor.jsf.FlowPhaseListener</phase-listener>
</lifecycle>
然后你需要定义控制器逻辑的流程定义。这是“sellitem”流程定义,它是一个 4 步的结账流程,包含一个动态导航规则,最终以确认销售交易结束。
<?xml version="1.0" encoding="UTF-8"?>
<flow xmlns="http://www.springframework.org/schema/webflow"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/webflow
http://www.springframework.org/schema/webflow/spring-webflow-1.0.xsd">
<var name="sale" class="org.springframework.webflow.samples.sellitem.Sale" scope="conversation" />
<start-state idref="enterPriceAndItemCount" />
<view-state id="enterPriceAndItemCount" view="/priceAndItemCountForm.jsp">
<transition on="submit" to="enterCategory"/>
</view-state>
<view-state id="enterCategory" view="/categoryForm.jsp">
<transition on="submit" to="requiresShipping" />
</view-state>
<decision-state id="requiresShipping">
<if test="${conversationScope.sale.shipping}" then="enterShippingDetails" else="processSale" />
</decision-state>
<view-state id="enterShippingDetails" view="/shippingDetailsForm.jsp">
<transition on="submit" to="processSale" />
</view-state>
<action-state id="processSale">
<bean-action bean="saleProcessor" method="process">
<method-arguments>
<argument expression="conversationScope.sale" />
</method-arguments>
</bean-action>
<transition on="success" to="showCostOverview" />
</action-state>
<end-state id="showCostOverview" view="/costOverview.jsp" />
</flow>
流程定义应在一个注册表中注册,以便它们可以执行
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:flow="http://www.springframework.org/schema/webflow-config"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/webflow-config
http://www.springframework.org/schema/webflow-config/spring-webflow-config-1.0.xsd">
<!-- Launches, continues, and refreshes flow executions -->
<flow:executor id="flowExecutor" registry-ref="flowRegistry"/>
<!-- Creates the registry of flow definitions eligible for execution in this application -->
<flow:registry id="flowRegistry">
<flow:location path="/WEB-INF/flows/sellitem-flow.xml" />
</flow:registry>
</beans>
注册后,只需将浏览器指向 FacesServlet,并将流程标识符作为输入即可启动流程。
http://localhost:8080/sellitem-jsf/servlet.faces?_flowId=sellitem-flow
值得注意的是,你可以配置流程定义和执行 URL 的格式——例如,启用 REST 风格的 URL,而不是默认基于请求参数的 URL。
如前所述,JSF 视图只是普通的 JSF 视图,无论是基于 JSP 还是基于 Facelet,都使用标准的 JSF 绑定表达式来访问会话状态,并使用标准的 UI 命令来发送 Web Flow 事件。sellitem-jsf 示例使用基于 JSP 的视图。
<f:view>
<div id="content">
<h2>Enter price and item count</h2>
<hr>
<table>
<h:form id="priceAndItemCountForm">
<tr>
<td>Price:</td>
<td>
<h:inputText id="price" value="#{sale.price}" required="true">
<f:validateDoubleRange minimum="0.01"/>
</h:inputText>
</td>
<td>
<h:message for="price" errorClass="error"/>
</td>
</tr>
<tr>
<td>Item count:</td>
<td>
<h:inputText id="itemCount" value="#{sale.itemCount}" required="true">
<f:validateLongRange minimum="1"/>
</h:inputText>
</td>
<td>
<h:message for="itemCount" errorClass="error"/>
</td>
</tr>
<tr>
<td colspan="2" class="buttonBar">
<h:commandButton type="submit" value="Next" action="submit"/>
</td>
<td></td>
</tr>
</h:form>
</table>
</div>
</f:view>
就是这样!你可以 试用该应用,亲自看看 Web Flow 如何处理导航和应用控制器逻辑,而 JSF UI 组件则负责内容渲染、数据绑定和验证行为。它们确实非常契合。
Spring Web Flow 1.1 的开发工作已经开始,更深层次的集成是令人兴奋的路线图中的一个主要主题。在 JSF 方面值得注意的是,我们将在流程定义中添加对统一表达式语言(EL)的支持,以及支持利用 Spring 2.0 作为跨所有作用域(包括 Spring Web Flow 提供的会话作用域)的 JSF 受管 Bean 的全面提供者。这项工作是除了核心新功能之外的补充,这些新功能将使所有环境中的 Web Flow 用户受益,例如支持会话作用域的持久化上下文。请参与论坛上的 1.1 路线图讨论,获取更多信息并参与其中!
我也想借此机会鼓励那些已经在 JSF 环境中使用 Spring Web Flow 的人,分享你们的经验——给我发电子邮件,在这里留言,在 JSF central 上写文章,告诉 JSF 社区的领导者你们的经验。在规范负责人 寻求社区反馈 的时期,你们的实际经验有助于影响 JSF 2.0 规范的方向。Interface21 已收到 JSF 规范负责人 Ed Burns 的邀请,加入 JSF 2.0 专家组,这是对 Web Flow 作为创新性 JSF 扩展所做贡献的认可。我们已经接受了邀请,很高兴能帮助将导航和状态管理领域已被证明行之有效的一般性经验引入 JSF 2.0,同时继续开拓新领域,并在任何环境中保持可用性。