《Spring 实战》-- Spring Web Flow 学习笔记

Spring Web FlowSpring MVC的扩展,它支持开发基于流程的应用程序.它将流程的定义与实现流程行为的类和视图分离开来.

Spring中配置Web Flow

Spring Web Flow是构建于Spring MVC的基础上的,这意味着所有的流程请求都需要首先经过DispatcherServlet.我们需要在Spring应用上下文中配置一些Bean来处理流程并执行请求.

首先,我们在Spring的基础上会多使用到4个jar包,并且会使用到flow命名空间

jar包

配置

做好了准备工作,让我们一起来看一下配置

装配流程执行器

Spring配置文件中使用<flow:flow-executor>元素创建一个流程执行器.

正如其名,流程执行器驱动流程的执行,用户进入一个流程时,流程执行器会为用户创建并启动一个流程执行实例,当流程暂停的时候(如为用户展示视图时),流程执行器会在用户执行操作后恢复流程.流程执行器只负责创建和执行流程,但它并不负责加载流程定义.这个任务由流程注册表完成

1
<flow:flow-executor id="flowExecutor"/>

配置流程注册表

Spring配置文件中使用<flow:flow-register>配置流程注册表,流程注册表的工作是加载定义并让流程执行器能够使用它们.

1
2
3
<flow:flow-registry id="flowRegistry" base-path="/WEB-INF/flows">
<flow:flow-location-pattern value="/**/*-flow.xml" />
</flow:flow-registry>

如果不使用通配符,这里也可以使用<flow:flow-location id="pizza" path="/WEN-INF/flows/springpizza.xml"/>来指定流程配置文件的详细位置,当不显示指定id时,以文件名作为id的值

处理流程请求

Spring MVC的基础上,还需要一个FlowHandlerMapping来帮助DispatcherServlet发送流程请求给spring web flow,在Spring的上下文中添加如下配置

1
2
3
<bean class="org.springframework.webflow.mvc.servlet.FlowHandlerMapping">
<property name="flowRegistry" ref="flowRegistry" />
</bean>

可以看到,它装配了流程注册表的引用,这样它就能知道如何将请求的URL匹配到流程上

FlowHandlerMapping仅是对流程请求的处理,响应请求由FlowHandlerAdapter处理,它会把响应发送的流程请求进行处理,在Spring的上下文中添加如下配置:

1
2
3
<bean class="org.springframework.webflow.mvc.servlet.FlowHandlerAdapter">
<property name="flowExecutor" ref="flowExecutor" />
</bean>

这样,所有的准备工作都已经做完了!但这还不够,在定义流程前,我们先来看一下流程组件

流程组件

Spring Web Flow中,流程是由三个主要元素定义的:状态、转移和流程数据.

状态(state)

即流程中事件的发生地点,如果将流程比喻成公路旅行,那么状态就是路途上的城镇或风景点,一共有五种不同的状态

状态类型 作用
行为(action) 行为状态是流程逻辑发生的地方
决策(decision) 决策状态将流程分成两个方向,它会基于流程数据的评估结果确定流程方向
结果(end) 结束状态是流程的最后一站,一旦进入End状态,流程就会终止
子流程(subflow) 子流程状态会在当前正在运行的流程上下文中启动一个心的流程
视图(view) 视图状态会暂停流程并邀请用户参与流程

视图状态

视图状态用于为用户展现信息并使用户在流程中发挥作用,可以是任意Spring支持的视图类型.在下面的示例中,id在流程内标识这个状态,当不使用view显示指定另一个视图名称时,它也代表视图的名称,model指明表单所绑定的对象

1
<view-state id="welcome" view="greeting" model="flowScope.paymentDetails"/>

行为状态

行为状态是应用程序自身在执行任务,行为状态一般会触发Spring所管理的Bean的一些方法并根据方法调用的执行结果转移到另一个状态.如下例,其中的evaluate子节点代表行为状态要做的事,在此处它会找到idpizzaFlowActionsbean并执行saveOrder()方法

1
2
3
4
<action-state id="saveOrder">
<evaluate expression="pizzaFlowActions.saveOrder(order)" />
<transition to="thankYou" />
</action-state>

决策状态

决策状态能够在流程执行时产生两个分支.决策状态将评估一个Boolean类型的表达式,然后在两个状态中选择转移一个.可以看到,if子节点是这个状体的核心,test中的表达式值为true则转移到then指定状态,否则转移到else指定状态

1
2
3
4
5
<decision-state id="checkDeliveryArea">
<if test="pizzaFlowActions.checkDeliveryArea(order.customer.zipCode)"
then="addCustomer"
else="deliveryWarning"/>
</decision-state>

子流程状态

就像把代码逻辑分写到多个类/方法中一样,流程也将其分成一些子流程.如下例中子流程结束的<endstate>idorderCreated,则流程将会进入到名为payment的状态

1
2
3
4
<subflow-state id="customer" subflow="pizza/customer">
<input name="order" value="order"/>
<transition on="customerReady" to="order" />
</subflow-state>

结束状态

所有的流程均会结束,这就是结束状态要做的事情.

1
<end-state id="customerReady">

在流程结束后,接下来会发生什么取决于以下因素:

  1. 如果结束流程是一个子流程,那么调用它的流程将会从<subflow-state>处继续执行,<end-state>id将会用作事件触发<subflow-state>开始的转移
  2. 如果<end-state>设置了view属性,指定的视图将会被渲染.视图可以是相对于流程路径的视图模版,如果添加externalRedirect:前缀话,将会重定向到流程外部的页面,如果添加flowRedirect:将会重定向到另一个流程中
  3. 如果结束的流程不是子流程,也没有指定view属性,那这个流程只是会结束而已,浏览器最后将会加载流程的基本URl地址,当前已没有活动的流程,所以会开始一个新的流程实例

==流程可能会有不止一个结束状态,子流程的结束状态id确定了激活的事件,所以可以通过多钟结束状态来结束子流程,从而能够在流程中触发不同的事件,即使不是在子流程中,在结束流程后也可以根据流程的执行情况进行多个页面的选择==

转移

流程中除结束状态之外的每个状态都至少需要一个转移,使用<transition>子节点来定义,在前面可能你已经看到了他的身影.它是action-state/view-state/subflow-state的子节点,其中id可以是用户触发的事件,在子流程中,事件取决于子流程结束的id,任何事件都可以使用no来触发转移事件,当抛出异常时,可以使用on-exception来指定异常的转移状态.

1
<transition to="lookupCustomer" id="phoneEntered"/>

全局转移:在多个状态中都需要用到的转移,可以定义成全局转移

1
2
3
<global-transitions>
<transition on="cancel" to="endState" />
</global-transitions>

流程数据

定义变量

流程数据保存在变量中,而变量可以在流程的各个地方进行引用,它能够以多钟方式创建,在流程中创建变量的最简单形式就是使用<var>元素

1
<var name="order" class="com.springinaction.pizza.domain.Order"/>

当然,也可以使用<evaluate>元素计算并创建一个变量

1
2
<evaluate result="viewScope.toppingsList"
expression="T(com.springinaction.pizza.domain.Topping).asList()" />

也可以使用<set>元素设置变量的值

1
2
<set name="flowScope.pizza"
value="new com.springinaction.pizza.domain.Pizza()" />

后面两个特别类似,都是通过计算生成结果

流程数据的作用域

范围 作用域与可见性
Conversation 最高层级的流程开始创建,在最高层级的流程结束时销毁,被最高层级的流程与子流程共享
Flow 当流程开始时创建,在流程结束时被销毁,只有在创建它的流程中是可见的
Request 当一个请求进入流程的时候创建,在流程返回时被销毁
Flash 当流程开始的时候创建,在流程结束的时候销毁,在视图状态渲染后,它也会被清除
View 当进入视图状态时创建,当这个状态退出的时候销毁,只在视图状态内可见

当使用<var>元素声明时,变量始终是流程作用域的,当使用<set><evaluate>的时候,作用域通过nameresult属性的前缀指定


以上就是Spring Web Flow 的基础知识,至于Demo可以在《Spring 实战》的指定网站上下载源码,当然啦,也可以百度一下,为了方便起见,可以直接去我建的仓库克隆一下,传送门,这是Spring Web Flow的Demo ,不是全部的源码.

文章目录
  1. 1. 配置
    1. 1.1. 装配流程执行器
    2. 1.2. 配置流程注册表
    3. 1.3. 处理流程请求
  2. 2. 流程组件
    1. 2.1. 状态(state)
    2. 2.2. 转移
    3. 2.3. 流程数据
|