本章主要内容:
Spring Web Flow 是Spring MVC 的扩展,它支持开发基于流程的应用程序。它将流程的定义与实现流程行为的类和视图分离开来。
Spring Web Flow是构建于Spring MVC基础之上的。这意味着所有的流程请求都需要首先经过Spring MVC的DispatcherServlet。我们需要在Spring应用上下文中配置一些bean来处理流程请求并执行流程。现在,还不支持在Java中配置Spring Web Flow,所以我们需要在XML中对其进行配置。有一些bean会使用Spring Web Flow的Spring配置文件命名空间来进行声明。因此需要在上下文定义XML文件中添加这个命名空间声明:
在声明了命名空间之后,就为装配Web Flow的bean做好了准备。首先从流程执行器(flow executor)开始。
流程执行器驱动流程的执行。当用户进入一个流程时,流程执行器会为用户创建并启动一个流程执行实例。当流程暂停的时候(如为用户展示视图时),流程执行器会在用户执行操作后恢复流程。
在Spring中,
尽管流程执行器负责创建和执行流程,但它并不负责加载流程定义。这个责任落在流程注册表(flow registry)身上。
流程注册表(flow registry)的工作是加载流程定义并让流程执行器能够使用它们、我们可以在Spring中使用
在这个声明中,流程注册表会在”/WEB-INF/flows”目录下查找流程定义,这是通过base-path属性指明的。根据
所有的流程都是通过其ID来进行引用的。这里我们使用了
/WEB-INF/flows/order/order-flow.xml
在这个配置中,”/WEB-INF/flows”代表流程注册表基本路径,”order”表示流程ID,”order-flow.xml”流程定义文件。
作为另外一种方式,我们可以去除base-path属性,而显示声明流程定义文件的位置:
在这里,使用了
DispatcherServlet用于将请求分发给控制器。但是对于流程而言,我们需要一个FlowHandlerMapping来帮助DispatcherServlet将流程请求发送给Spring Web Flow。在Spring应用上下文中,FlowHandlerMapping的配置如下:
可以看到FlowHandlerMapping装配了注册表的引用,这样它就能知道如何将请求的URL匹配到流程上。例如,如果我们有一个ID为pizza的流程,FlowHandlerMapping就会知道如果请求的URL模式(相对于应用程序的上下文路径)是”/pizza”的话,就要将其匹配到这个流程上。
然而,FlowHandlerMapping的工作仅仅是将流程请求定向到Spring Web Flow上,响应请求的是FlowHandlerAdapter。FlowHandlerAdapter等同于Spring MVC的控制器,它会响应发送的流程请求并对其进行处理。FlowHandlerAdapter可以向下面这样装配成一个Spring bean,如下:
这个处理适配器是DispatcherServlet和Spring Web Flow之间的桥梁。它会处理流程请求并管理基于这些请求的流程。在这里,它装配了流程执行器的引用,而后者是为所处理的请求执行流程的。
我们已经配置了Spring Web Flow 所需的bean和组件。剩下的就是真正定义流程了。在此之前,我们先了解一下组成流程的元素。
在Spring Web Flow中,流程是由三个主要元素定义的:状态、转移和流程数据。
Spring Web Flow定义了五种不同类型的状态。如下表所示,通过选择Spring Web Flow的状态几乎乐意把任意的安排功能构造成会话式的Web应用。
状态类型 | 用途 |
---|---|
行为(Action) | 行为状态是流程逻辑发生的地方 |
决策(Decision) | 决策状态将流程分成两个方向,它会基于流程数据的评估结果确定流程方向 |
结束(End) | 结束状态是流程的最后一站。一旦进入End状态,流程就会终止 |
子流程(Subflow) | 子流程状态会在当前正在运行的流程上下文中启动一个新的流程 |
视图(View) | 视图状态会暂停流程并邀请用户参与流程 |
视图状态用于为用户展现信息并使使用在流程中发挥作用。实际的视图可以是Spring支持的任意视图类型,但通常是用JSP来实现的。在流程定义的XML文件中,
在这个简单的示例中,id属性有两个含义。它在流程内标识这个状态,除此之外,因为在这里没有其他地方指定视图,所以它也指定了流程到达这个状态时要展现的逻辑视图名为welcome。如果希望显示的指定另一个视图名,则可以使用view属性:
如果流程为用户展现了一个表单,写昂指定表单所绑定的对象,可以通过model属性进行设置:
这里我们指定takePayment视图中的表单将绑定流程作用域内的paymentDetails对象。
视图状态会涉及到流程应用程序的用户,而行为状态则是应用程序自身在执行任务。行为状态一般会触发Spring所管理bean的一些方法并根据方法调用的执行结果转移到另一个状态。在流程定义XML中,行为状态使用
尽管不是严格要求,但是
有可能流程会完全按照线性执行,从一个状态进入另一个状态,没有其他的替代路线。但是更常见的情况是流程在某一个点根据流程的当前情况进入不同的分支。决策状态能够在流程执行中产生两个分支。决策状态将评估一个Boolean类型的表达式,然后在两个状态中选择一个。这要取决于表达式会计算出true还是false。在XML流程定义中,决策状态通过
可以看到
将流程分成独立的部分是很好的实现。
在这里元素用于传递订单对象作为子流程的输入。如果子流程结束的
最后,所有的流程都结束了。这就是当流程转移到结束状态时所做的。
当到达
需要意识到流程可能会不止一个结束状态。子流程的结束状态ID确定了激活的事件,所以可能希望通过多种结束状态来结束子流程,从而能够在调用流程过程中触发不同的事件。即使不是在子流程中,也有可能在结束流程后,根据流程的执行情况有多个显示页面供选择。
现在,已经看完了流程中的各个状态。接下来应该看一下流程如何在状态间迁移的。
转移连接了流程中的状态。流程中除结束状态之外的每个状态,至少都需要一个转移,这样就能够知道一旦这个状态完成时流程要去向哪里。状态可以有多个转移,分别对应于当前状态结束时可以执行的不同的路径。转移使用
属性to用于指定流程的下一个状态。如果
在本例中,如果触发了phoneEntered事件,流程将会进入lookupCustomer状态。
在抛出异常时,流程也可以进入另一个状态。例如,如果顾客的记录没有找到,你可能希望流程转移到一个展现注册表单的视图状态。如下所示:
属性on-exception类似于to属性,只不过它指定了要发生转移的异常而不是一个事件。
在流程创建完成后可能会发现有一些状态使用了一些通用的转移。与其在多个状态中都重复通用的转移,我们可以将
定义完这个全局转以后,流程中的所有状态都会默认拥有这个cancel转移。
当流程从一个状态进入到另一个状态时,它会带走一个数据。有时候,这些数据只需要很短的时间(可能只要展现页面给用户)。有时候,这些数据会在整个流程中传递并在流程结束的时候使用。
流程数据保存在变量中,而变量可以在流程的各个地方进行引用。它能够以多种方式创建。在流程中创建变量的最简单形式是使用元素:
这里,创建了一个新的Customer实例并将其放在名为customer的变量中。这个变量可以在流程的任意状态进行访问。作为行为状态的一部分或者作为视图状态的入口,有可能会使用
在本例中,
类似地,
流程中携带的数据会拥有不同的生命作用域和可见性,这取决于保存数据的变量本身的作用域。Spring Web Flow定义了五种不同作用域:
范围 | 生命作用域和可见性 |
---|---|
Conversation | 最高层级的流程开始时创建,在最高层级的流程结束时销毁。被最高层级的流程和其所有的子流程所共享 |
Flow | 当流程开始时创建,在流程结束时销毁。只有在创建它的流程中是可见的 |
Request | 当一个请求进入流程时创建,在流程返回时销毁 |
Flash | 当流程开始时创建,在流程结束时销毁。在视图状态渲染后,它也会被清除 |
View | 当进入视图状态时创建,当这个状态退出时销毁。只在视图状态内是可见的 |
当使用元素声明变量时,变量始终是流程作用域的,也就是在定义变量的流程内有效。当使用