领先一步
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 中控制器功能的单元——例如账户注册向导或客户主/详细信息编辑器——称为流程定义。此类控制器的运行时实例称为流程执行。在运行时启动新的流程执行,以允许单个用户参与应用程序的对话。流程执行负责选择用户的初始视图,然后响应用户事件来执行应用程序行为并确定要显示的下一个视图。它还管理与用户对话相关的状态。
将 Web Flow 与 JSF 集成的一个重要部分是将流程执行生命周期融入 JSF 生命周期。这部分是通过实现一个自定义 PhaseListener 来实现的,该监听器负责在客户端请求时启动新的流程执行,以及在 JSF 视图恢复期间恢复现有的流程执行。
例如,通过访问一个 URL,如
https://: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 组件可以以线程安全的方式无缝地更新流程管理的 State(无双关之意)。
一旦用户决定调用 UI 命令,例如通过单击命令链接,标准的 JSF 回发生命周期就会启动。在处理完 UI 组件验证和更新模型值之后,JSF 操作结果会被视为一个事件,针对恢复的流程执行的视图状态发出。然后,流程将接管,通过调用适当的应用程序行为并根据流程定义的导航规则选择下一个视图来处理该事件。此 Web Flow 导航步骤由 JSF 集成中的最后一个关键构造——一个自定义 NavigationHandler——来处理。
总而言之,从 JSF 视图和组件开发者的角度来看,这只是标准的 JSF——无论您使用的是 JSP 还是带有 XYZ 组件提供商的 Facelets,这都没关系。Web Flow 执行会自动为您恢复,并且对会话状态的访问完全透明。Web Flow 事件只是标准的 JSF UI 命令结果。从控制器开发者的角度来看,一切都是 Web Flow。因此,faces-config.xml 中冗长且受限的导航规则被模块化、简洁的流程定义所取代,这些定义基于更丰富的流程定义语言。最终,您将获得原生 JSF 组件模型的所有优势,以及 Web Flow 导航模型的所有优势。
在 JSF 环境中开始使用 Spring Web Flow 的最佳方法是下载 1.0.3 版本并部署 sellitem-jsf 示例,该示例也可在网上进行测试。该示例可作为 Eclipse Dynamic Web Project 导入,以便在 Eclipse 中轻松部署。此外,该示例支持 Spring IDE 2.0,这是一个 Eclipse 插件,其中包括一个图形化 Web Flow 编辑器。
为了结束本文,我将简要介绍如何在 JSF 环境中使用 sellitem-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 并将流程标识符作为输入即可启动流程。
https://: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 作为 JSF 管理 Bean 的全面提供商,覆盖所有范围——包括 Spring Web Flow 提供的会话范围。这项工作以及为所有环境的 Web Flow 用户带来好处的新核心功能,例如对会话范围持久化上下文的支持。请参与论坛上的1.1 路线图讨论,以获取更多信息并参与其中!
我想借此机会鼓励那些已经在 JSF 环境中使用 Spring Web Flow 的人分享您的经验——给我发邮件,在这里发表评论,在 JSF 中心写文章,告诉 JSF 社区的领导者您的经验。您的实际经验可以帮助影响 JSF 2.0 规范的方向,因为该规范的负责人已经征求了社区反馈。Interface21 已收到 JSF 规范负责人 Ed Burns 的邀请,加入 JSF 2.0 专家组,这认可了 Web Flow 作为创新 JSF 扩展的贡献。我们已接受邀请,并热衷于将已被证明在导航和状态管理方面行之有效的方法回馈给 JSF 2.0,同时继续开辟新领域并保持在任何环境中的可用性。