Spring Web Flow 为 JSF 开发者提供的功能

工程 | Keith Donald | 2007年4月21日 | ...

Spring Web Flow,与 Spring 框架本身一样,是一种独特的集成技术。大多数用户将其视为通用的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 现有的请求、会话和应用程序作用域。这些作用域是:

  • 会话作用域 - 在用户对话期间存在的已管理作用域
  • 流程作用域 - 在会话中执行的流程期间存在的已管理作用域
  • 闪存作用域 - 在参与流程的视图期间存在的已管理作用域
这些作用域是“已管理的”,因为它们会由 Web Flow 自动清除。会话作用域非常适合存储要在用户交互结束时持久化的模型状态,例如帐户注册向导。闪存作用域已被证明对集成 Ajax 库(如 Ajax4JSF 和 ICEFaces)的用户非常有用,在这些库中,来自同一视图的多个请求是常态。在这种情况下,闪存作用域允许跨越来自同一页面的细粒度 Ajax 请求操作模型状态,然后在下次导航时自动清除。

回到我们主要用户所说的话:

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://127.0.0.1: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 还是带有 XYZ 组件提供程序的 Facelets 都无关紧要。流程执行会自动为您恢复,并且对会话状态的访问是完全透明的。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,这是一个 Eclipse 插件,其中包含图形化 Web Flow 编辑器

我将简要介绍使用 sellitem-jsf 示例在 JSF 环境中开始使用 Web Flow 的方法,以结束此条目。

设置 Faces 配置(样板)

要插入 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”流程定义,这是一个包含四个步骤的结账流程,只有一个动态导航规则,最终以已确认的销售交易结束。


<?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>
设置 Web 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://127.0.0.1:8080/sellitem-jsf/servlet.faces?_flowId=sellitem-flow

值得注意的是,您可以配置流程定义和执行 URL 的格式 - 例如,启用 REST 风格的 URL 来代替默认的基于请求参数的 URL。

实现 JSF 视图

如前所述,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 和 JSF 构建应用程序而取得的。特别感谢 Jeremy Grelle、James Clinton、Ed Burns、Craig McClanahan 和 Colin Sampaleanu 为使 Spring Web Flow JSF 集成达到今天的坚实水平所做出的所有贡献。

Spring Web Flow 1.1 的工作已经开始,更高级别的集成是令人兴奋的路线图中的一个主要主题。在 JSF 方面值得注意的是,我们将添加对流程定义中统一表达式语言 (EL) 的支持,以及对利用 Spring 2.0 作为所有作用域(包括 Spring Web Flow 提供的会话作用域)的 JSF 托管 Bean 的全面提供者的支持。这项工作是对将使所有环境中的 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 中,同时继续开拓新的领域并在任何环境中保持可用性。

获取 Spring 新闻通讯

关注 Spring 新闻通讯

订阅

领先一步

VMware 提供培训和认证,以加速您的进步。

了解更多

获取支持

Tanzu Spring 在一个简单的订阅中提供对 OpenJDK™、Spring 和 Apache Tomcat® 的支持和二进制文件。

了解更多

即将举行的活动

查看 Spring 社区中所有即将举行的活动。

查看全部