spring 中配置 WebFlow
现在,还不支持在Java中配置Spring Web Flow,所以我们别无选择,只能在XML中对其进行配置。有一些bean会使用Spring Web Flow的
Spring配置文件命名空间来进行声明。因此,我们需要在上下文定义XML文件中添加这个命名空间声明:
<?xml version="1.0" encoding="UTF-8"?>
<bens 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/webflow-config
http://www.springframework.org/schema/webflow-config/[CA]spring-webflow-config-2.3.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
装配流程执行器
流程执行器(flow executor)驱动流程的执行。当用户进入一个流程时,流程执行器会为用户创建并启动一个流程执行
实例。当流程暂停的时候(如为用户展示视图时),流程执行器会在用户执行操作后恢复流程。
在Spring中,<flow:flow-executor>元素会创建一个流程执行器:
<flow:flow-executor id = "flowExecutor"/>
配置流程注册表
流程注册表(flow registry)的工作是加载流程定义并让流程执行器能够使用它们。我们可以在Spring中使用<flow:flow-registry>
配置流程注册表,如下所示:
<flow:flow-registry id="flowRegistry" base-path="/WEB-INF/flows">
<flow:flow-location-pattern value="*-flow.xml"/>
</flow:flow-registry>
在这里的声明中,流程注册表会在“/WEB-INF/flows”目录下查找流程定义,这是通过base-path属性指明的。依据<flow:flowlocation-
pattern>元素的值,任何文件名以“-flow.xml”结尾的XML文件都将视为流程定义。
作为另一种方式,我们可以去除base-path属性,而显式声明流程
定义文件的位置:
<flow:flow-registry id="flowRegistry">
<flow:flow-location path="WEB-INF/flows/springpizza.xml"/>
</flow:flow-registry>
如果你希望更显式地指定流程ID,那你可以通过<flow:flowlocation>元素的id属性来进行设置。例如,要将pizza作为流程
ID,可以像这样配置:
<flow:flow-registry id="flowRegistry">
<flow:flow-location id="pizza"
path="WEB-INF/flows/springpizza.xml"/>
</flow:flow-registry>
处理流程请求
是对于流程而言,我们需要一个FlowHandlerMapping来帮助DispatcherServlet将流程请求发送给Spring Web Flow。
在Spring应用上下文中,FlowHandlerMapping的配置如下:
<bean class= "org.springframework.webflow.mvc.servlet.FlowHandlerMapping">
<property name="flowRegistry" ref="flowRegistry"/>
</bean>
Spring Web Flow上,响应请求的是FlowHandlerAdapter。FlowHandlerAdapter等同于Spring
MVC的控制器,它会响应发送的流程请求并对其进行处理。FlowHandlerAdapter可以像下面这样装配成一个Spring
bean,如下所示:
<bean class="org.springframework.webflow.mvc.servlet.FlowHandlerAdapter">
<property name="flowExecutor" ref="flowExecutor"/>
</bean>
这个处理适配器是DispatcherServlet和Spring Web Flow之间的桥梁。它会处理流程请求并管理基于这些请求的流程。在这里,它装
配了流程执行器的引用,而后者是为所处理的请求执行流程的。
流程的组件
流程是由三个主要元素定义的:状态、转移和流程数据。状态(State)是流程中事件发生的地点。如果你将流程想
象成公路旅行,那状态就是路途上的城镇、路边饭店以及风景点。流程中的状态是业务逻辑执行、做出决策或将页面展现给用户的地方,
而不是在公路旅行中买Doritos薯片和健怡可乐的所在。如果流程状态就像公路旅行中停下来的地点,那转移(transition)就
是连接这些点的公路。在流程中,你通过转移的方式从一个状态到另一个状态。
当你在城镇之间旅行的时候,你可能要买一些纪念品,留下一些记忆并在路上取一些空的零食袋。类似地,在流程处理中,它要收集一些
数据:流程的当前状况。我很想将其称为流程的状态,但是在我们讨论流程的时候状态(state)已经有了另外的含义。
状态
视图状态
在流程定义的XML文件中,<view-state>用于定义视图状态:
<view-state id="welcome"/>
如果你愿意显式指定另外一个视图名,那可以使用view属性做到这
一点:
<view-state id="welcome" view="greeting"/>
如果流程为用户展现了一个表单,你可能希望指明表单所绑定的对
象。为了做到这一点,可以设置model属性:
<view-state id="takePayment" view="flowScope.paymentDetails"/>
行为状态
在流程定义XML中,行为状态使用<action-state>元素来声明。
<action-state id="saveOrder">
<evaluate expression="pizzaFlowActions.saveOrder(order)"/>
<transition to="thankYou"/>
</action-state>
尽管不是严格需要的,但是<action-state>元素一般都会有一个<evaluate>作为子元素。<evaluate>元素给出了行为状态要做的事情。expression属性指定了进入这个状态时要评估的表达式。在本示例中,给出的expression是SpEL表达式,它表明将会找到ID为pizzaFlowActions的bean并调用其saveOrder()方法。
决策状态
决策状态能够在流程执行时产生两个分支。决策状态将评估一个Boolean类型的表达式,然后在两个状态转移中选择一个,这要取决于表达式会计算出true还是false。在XML流程定义中,决策状态通过<decision-state>元素进行定义。典型的决策状态示例如下所示:
<decision-state id="checkDeliveryArea">
<if test="pizzaFlowActions.checkDeliveryArea(customer.zipCode)"
then="addCustomer"
else="deliveryWarning"/>
</decision-state>
你可以看到,<decision-state>并不是独立完成工作的。<if>元素是决策状态的核心。这是表达式进行评估的地方,如果表达式结果为true,流程将转移到then属性指定的状态中,如果结果为false,流程将会转移到else属性指定的状态中。
子流程状态
将流程分成独立的部分是个不错的主意。<subflow-state>允许在一个正在执行的流程中调用另一个流程。这类似于在一个方法中调用另一个方法。
<subflow-state id="order" subflow="pizza/order">
<input name="order" value="order"/>
<transition on="orderCreated" to="payment"/>
</subflow-state>
在这里,<input>元素用于传递订单对象作为子流程的输入。如果子流程结束的<end-state>状态ID为orderCreated,那么流程将会转移到名为payment的状态。
结束状态
最后,所有的流程都要结束。这就是当流程转移到结束状态时所做的。<end-state>元素指定了流程的结束,它一般会是这样声明的:
<end-state id="customerReady">
当到达<end-state>状态,流程会结束。接下来会发生什么取决于
几个因素:
- 如果结束的流程是一个子流程,那调用它的流程将会从<subflow-state>处继续执行。<end-state>的ID将会用作事件触发从<subflow-state>开始的转移。
- 如果<end-state>设置了view属性,指定的视图将会被渲染。视图可以是相对于流程路径的视图模板,如果添加“externalRedirect:”前缀的话,将会重定向到流程外部的页面,如果添加“flowRedirect:”将重定向到另一个流程中。
- 如果结束的流程不是子流程,也没有指定view属性,那这个流程只是会结束而已。浏览器最后将会加载流程的基本URL地址,当前已没有活动的流程,所以会开始一个新的流程实例。
转移
转移使用<transition>元素来进行定义,它会作为各种状态元素(<action-state>、<view-state>、<subflow-state>)的子元素。最简单的形式就是<transition>元素在流程中指定下一个状态:
<transition to="customerReady"/>
更常见的转移定义是基于事件的触发来进行的。在视图状态,事件通常会是用户采取的动作。在行为状态,事件是评估表达式得到的结果。而在子流程状态,事件取决于子流程结束状态的ID。在任意的事件中(这里没有任何歧义),你可以使用on属性来指定触发转移的事件:
<transition on="phoneEntered" to="lookupCustomer"/>
在抛出异常时,流程也可以进入另一个状态。例如,如果顾客的记录没有找到,你可能希望流程转移到一个展现注册表单的视图状态。以下的代码片段显示了这种类型的转移:
<transiton on-exception= "com.springinaction.pizza.service.CustomerNotFoundException" to="registrationForm"/>
全局转移
在创建完流程之后,你可能会发现有一些状态使用了一些通用的转移。例如,如果在整个流程中到处都有如下<transition>的话,我一点也不感觉意外:
<transition on="cancel" to="endState"/>
与其在多个状态中都重复通用的转移,我们可以将<transition>元素作为<global-transitions>的子元素,把它们定义为全局转移。例如:
<global-transitions>
<transition on="cancel" to="endState"/>
</global-transitions>
定义完这个全局转移后,流程中的所有状态都会默认拥有这个cancel转移。
流程数据
声明变量
流程数据保存在变量中,而变量可以在流程的各个地方进行引用。它能够以多种方式创建。在流程中创建变量的最简单形式是使用<var>元素:
<var name="customer" class="com.springinaction.pizza.domain.Customer"/>
作为行为状态的一部分或者作为视图状态的入口,你有可能会使用<evaluate>元素来创建变量。例如:
<evaluate result="viewScope.toppingsList"
expression="T(com.springination.pizza.domain.Topping).asList()"/>
在本例中,<evaluate>元素计算了一个表达式(SpEL表达式)并将结果放到了名为toppingsList的变量中,这个变量是视图作用域的。
类似地,<set>元素也可以设置变量的值:
<set name="flowScope.pizza"
value="new.com.springination.pizza.domain.Pizza()"/>