第四版的第八章内容与第三版基本一致。
本章内容:
- 创建会话式web应用程序
- 定义流程状态和行为
- 保护web流程
互联网的一个奇特之处就在于它很容易让人迷失。有如此多的内容可以查看和阅读,而超链接是其强大魔力的核心所在。
有时候,web应用程序需要控制web冲浪者的导向,引导他们一步步地访问应用。比如电子商务网站的付款流程,从购物车开始,应用程序会引导你依次经过配送详情、账单信息以及最终的订单确认。
Spring Web Flow是一个web框架,它适用于元素规定流程运行的程序。本章中,我们将会探索它是如何用于Spring Web框架平台的。
其实我们可以使用任何的Web框架编写流程化的应用程序,比如使用Struts构建特定的流程。但是这样没有办法将流程与实现分开,你会发现流程的定义分散在组成流程的各个元素中,没有特定的地方能够完整地描述整个流程。
Spring Web Flow是Spring MVC的扩展,它支持开发基于流程的应用程序,可以将流程的定义和实现流程行为的类和视图分离开来。
在介绍Spring Web Flow的时候,我们会暂且放下Spittr样例,而使用生产披萨订单的web程序。
使用的第一步是在项目中进行安装,那么就从安装开始吧。
在Spring中配置Spring Web Flow
Spring Web Flow是基于Spring MVC构建的,这就意味着所有的流程请求都需要经过Spring MVC的DispatcherServlet
。我们需要在Spring应用上下文中配置一些Bean来处理流程请求并执行流程。
现在还没有支持使用Java来配置Spring Web Flow,所以没得选,只能在XML中进行配置。有一些Bean会使用Spring Web Flow的Spring配置文件命名空间来进行声明,因此我们需要在上下文定义XML文件中添加相应的命名空间:
<?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"
xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/webflow-config
http://www.springframework.org/schema/webflow-config/spring-webflow-config-2.3.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
声明了命名空间后,就可以准备装配Web Flow的Bean了。
编写流程执行器
顾名思义,流程执行器(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:flow-location-pattern>
元素,任何以-flow.xml
结尾的XML文件都会被视为流程定义。
所有的流程都是通过其ID来进行引用的。使用<flow:flow-location-pattern>
元素,流程的ID就是相对于base-path
的路径,或者是双星号所代表的路径,如下图展示了流程ID是如何计算的:
另外,你也可以不使用base-path
属性,直接显式地声明流程定义文件的位置:
<flow:flow-registry id="flowRegistry">
<flow:flow-location path="/WEB-INF/flows/springpizza.xml" />
</flow:flow-registry>
这里使用了<flow:flow-location>
而不是<flow:flow-location-pattern>
,path
属性直接指定了/WEB-INF/flows/springpizza.xml
为流程定义文件。当这样定义时,流程的ID是从流程定义文件的文件名中获取的,这就是springpizza
。
如果你希望更显示地指定流程ID,那么可以通过<flow:flow-location>
元素的id属性来进行设置。例如,要设定pizza作为流程ID,可以这样进行配置:
<flow:flow-registry id="flowRegistry">
<flow:flow-location id="pizza"
path="/WEB-INF/flows/springpizza.xml" />
</flow:flow-registry>
处理流程请求
正如前面的章节中提到的,DispatcherServlet
会将请求分发给控制器,但是对于流程而言,你需要FlowHandlerMapping
来帮助DispatcherServlet
将流程请求发送给Spring Web Flow。FlowHandlerMapping
的配置如下:
<bean class="org.springframework.webflow.mvc.servlet.FlowHandlerMapping">
<property name="flowRegistry" ref="flowRegistry" />
</bean>
FlowHandlerMapping
装配了注册表的引用,这样它就知道如何将请求的URL匹配到流程上。例如,如果有一个ID为pizza的流程,FlowHandlerMapping
就会知道如果请求的URL是/pizza
的话,就会将其匹配到这个流程上。
然而,FlowHandlerMapping
的工作仅仅是将流程请求定向到Spring Web Flow,响应请求的是FlowHandlerAdapter
,它等同于Spring MVC的控制器,会对流程请求进行响应并处理。FlowHandlerAdapter
可以像下面这样装配成一个Spring Bean:
<bean class="org.springframework.webflow.mvc.servlet.FlowHandlerAdapter">
<property name="flowExecutor" ref="flowExecutor" />
</bean>
这个处理适配器就是DispatcherServlet
和Spring Web Flow之间的桥梁。它会处理流程请求并管理基于这些请求的流程。在这里,它装配了流程执行器的引用,而后者是为请求执行流程的。
现在已经配置了Spring Web Flow所需的Bean和组件,下面所需的就是真正的定义流程了。首先了解下流程的组成元素。
流程组件
在Spring Web Flow中,流程是由3个主要元素组成的:状态(state)、转移(transition)和流程数据(flow data)。状态
是流程中事件发生的地点。如果将流程想象成公路旅行,那么状态就是路途上的城镇、路边饭店以及风景点等。流程中的状态是业务逻辑执行、做出决策或将页面展示给用户的地方,而不是在公路旅行中买薯片或者可乐这些行为。
如果说流程状态是公路上停下来的地点,那么转移就是连接这些点的公路。在流程上,需要通过转移从一个状态到达另一个状态。
在城镇间旅行的时候,可能需要购买一些纪念品、留下一下回忆。类似的,在流程处理过程中,它要收集一些数据:流程当前状况等。也许你很想将其称为流程的状态,但是我们定义的状态已经有了另外的含义。
状态
Spring Web Flow定义了5种不同的状态,如下表所示。通过选择Spring Web Flow的状态几乎可以把任意的安排功能构造成会话式的Web应用程序。尽管并不是所有的流程都需要下表中的状态,但最终你可能会经常使用其中几个。
状态类型 | 作用 |
---|---|
行为(Action) | 流程逻辑发生的地方 |
决策(Decision) | 决策状态将流程分为两个方向,它会基于流程数据的评估结果确定流程方向 |
结束(End) | 结束状态是流程的最后一站,进入End状态,流程就会终止 |
子流程(Subflow) | 子流程状态会在当前正在运行的流程上下文中启动一个新的流程 |
视图(View) | 视图状态会暂停流程并邀请用户参与流程 |
首先了解下这些流程元素在Spring Web Flow定义中是如何表现的。
视图状态
视图状态用来为用户展现信息并使用户在流程中发挥作用。实际的视图实现可以是Spring支持的任意视图类型,但通常是用JSP来实现的。
在流程定义文件中,<view-state>
用来定义视图状态:
<view-state id="welcome" />
在这个简单的示例中,id属性有两个含义。其一,它定义了流程中的状态。其二,因为这里没有其他地方指定视图,那么它就指定了流程到达这个状态时要展现的逻辑视图名称为welcome。
如果要显示地指定另外一个视图名称,那么就可以使用view
属性:
<view-state id="welcome" view="greeting" />
如果流程为用户展现了一个表单,你希望指定表单所绑定的对象,可以使用model
属性:
<view-state id="takePayment" model="flowScope.paymentDetails"/>
这里指定了takePayment视图将绑定流程范围内的paymentDetails对象。
行为状态
视图状态包括流程应用的用户,而行为状态则是应用程序自身在执行任务。行为状态一般会触发Spring所管理Bean的一些方法,并跟你讲方法调用的执行结果转移到另一个状态。
在流程定义文件中,行为状态使用<action-state>
元素来声明:
<action-state id="saveOrder">
<evaluate expression="pizzaFlowActions.saveOrder(order)" />
<transition to="thankYou" />
</action-state>
尽管没有严格要求,但是<action-state>
元素一般都有一个<evaluate>
子元素,该元素给出了行为状态要做的事情,expression
属性指定了进入这个状态时要评估的表达式。本例中,给出的是SpEL表达式,这表明它将会找到ID为pizzaFlowActions的Bean,并调用其saveOrder()方法。
决策状态
流程有可能会按照线性执行下去,从一个状态到另一个状态,没有其他的替代路线。但是更常见的是流程在某一个点根据流程当前情况进入不同的分支。
决策状态能够使得在流程执行时产生两个分支,它会评估一个Boolean表达式,根据结果是true还是false在两个状态转移中选择一个。在流程定义文件中,使用<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=