19、使用Spring Web Flow(1)(Spring笔记)

Spring Web FlowSpring MVC的扩展,它支持开发基于流程的应用程序。比如在网上商城购买时,需要经过查看商品、添加、提价订单、付款等多个流程,有着严格的先后次序。这类应用场景可以使用Spring Web Flow构建。

一、在Spring 中配置 Web Flow

Spring Web Flow是构建于Spring MVC基础之上的。这标明所有的流程请求都需要首先经过DispatcherServlet。需要在Spring应用上下文中配置一些bean来处理流程请求并执行流程。现在,还不支持在Java中配置Spring Web Flow,因此,需要在上下文定义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">

1.1 装配流程执行器

流程执行器(flow executor)驱动流程的执行。当用户进入一个流程时,流程执行器会为用户创建并启动一个流程执行实例。当流程暂停的时候,流程执行器会在用户执行操作后恢复流程。创建方式如下:

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

注意:流程执行器负责创建和执行流程,但并不负责加载流程定义,此任务由流程注册表完成。

1.2 配置流程注册表

流程注册表(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"目录下查找流程定义。根据<flow:flow-location-pattern>元素的值,任何文件名以"-flow.xml"结尾的XML文件都将视为流程定义。

  • 所有的流程都是通过其ID来进行引用的。流程ID就是相对于base-path路径——或者双星号所代表的路径。如图所示,给出一个完整路径是如何得到流程ID的。

    1

作为另一种方式,我们可以去除base-path属性,显式声明流程定义文件的位置:

<flow:flow-registry id="flowRegistry">
   <flow:flow-location path="/WEB-INF/flows/springpizza.xml" />
</flow:flow-registry>

说明:此时,流程的ID是从流程定义文件的文件名中获得,此处为springpizza。当然也可以显式的指定流程ID

<flow:flow-registry id="flowRegistry">
   <flow:flow-location id="pizza" path="/WEB-INF/flows/springpizza.xml" />
</flow:flow-registry>

1.3 处理流程请求

对于流程而言,需要一个FlowHandlerMapping来帮助DispatcherServlet将流程请求发送给Spring Web Flow。配置如下:

<bean class="org.springframework.webflow.mvc.servlet.FlowHandlerMapping">
  <property name="flowRegistry" ref="flowRegistry" /><!--使用ref引用流程注册表-->
</bean>

说明:这里引用了流程注册表,如果我们有一个IDpizza的流程,FlowHandlerMapping就会知道如果请求的URL模式是"/pizza"的话,就要将其匹配到这个流程上。

FlowHandlerMapping的工作仅仅是将流程请求定向到Spring Web Flow,响应请求的是FlowHandlerAdapter,它会响应发送的流程请求并对其进行处理。配置如下:

<bean class="org.springframework.webflow.mvc.servlet.FlowHandlerAdapter">
  <property name="flowExecutor" ref="flowExecutor" /><!--使用ref引用流程执行器-->
</bean>

下面看看一个完整的流程请求响应过程:

  • 首先流程处理请求由DispatcherServlet转发给FlowHandlerMapping开始处理;
  • FlowHandlerMapping读取流程注册表,知道流程是如何定义、在哪里定义的,这相当于一种映射过程;
  • FlowHandlerMapping将具体的处理工作交给FlowHandlerAdapterFlowHandlerAdapter调用请求执行器执行具体的流程请求。

二、流程的组件

流程是由三个主要元素定义的:状态、转移和流程数据。状态是流程中事件发生的地点,是业务逻辑执行、做出决策或将页面展现给用户的地方;转移就是连接各状态的公路,通过转移从一个状态到另一个状态;流程数据就是在流程执行过程中当前的状况。

2.1 状态

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

2.1.1 视图状态

视图状态用于为用户展现信息并使用用户在流程中发挥作用。视图实现可以有多种,通常使用JSP来实现的。视图状态定义如下:

<view-state id="welcome" />

说明:id属性有两个含义,它在流程内标识这个状态。此外,因为在这里没有在其他地方指定视图,所以它也指定了流程到达这个状态时要展现的逻辑视图名为welcome

当然可以显式指定另外一个视图名,可以使用view属性做到这一点:

<view-state id="welcome" view="greeting" />

如果流程为了用户展现一个表单,我们可能希望指明表单所绑定的对象:

<view-state id="takePayment" model="flowScope.paymentDetails" />

这里指定takePayment视图中的表单将绑定流程作用域内的paymentDetails对象。

2.1.2 行为状态

视图状态会涉及到流程应用程序的用户,而行为状态则是应用程序自身在执行任务。行为状态一般会触发Spring所管理bean的一些方法并根据方法调用的执行结果转移懂啊另一个状态。如:

<action-state id="saveOrder">
    <evaluate expression="pizzaFlowActions.saveOrder(order)" />
    <transition to="thankYou" /><!--后面进行说明-->
</action-state>

说明:尽管不是严格要求,但是<action-state>元素一般都会有一个<evaluate>作为子元素,其作用就是给出了行为状态要做的事情。expression属性指定了进入这个状态时要评估(计算)的表达式。

2.1.3 决策状态

有可能流程会完全按照线性执行,从一个状态进入另一个状态,没有其他的替代路线。但是更常见的情况是流程在某一个点根据流程当前的情况进入不同的分支。

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

说明:很容易理解,test属性指的是一个判定。如果结果为true,则流程转移到then属性指定的状态中,否则,转移到else属性指定的状态。

2.1.4 子流程状态

我们不可能将应用程序的所有逻辑写在一个方法中,而是将其分散到多个类、方法以及其他结构中。<subflow-state>允许在一个正在执行的流程中调用另一个流程。这类似于在一个方法中调用另一个方法。

<subflow-state id="order" subflow="pizza/order">
  <input name="order" value="order"/>
  <transition on="orderCreated" to="payment" />
</subflow-state>

说明:<input>元素用于传递订单对象作为子流程的输入。如果子流程结束的<end-state>状态IDorderCreated,那么流程将会转移到payment的状态。这里的subflow属性应该是指定子流程定义路径。

2.1.5 结束状态

所有的流程都要结束。一般会这样声明:

<end-state id="customerReady" />

当到达此状态,流程会结束。接下来会发生什么取决于几个因素:

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

需要意识到流程可能会有不止一个结束状态。子流程的结束状态ID确定了激活的事件,所以可能会希望通过多种结束状态来结束子流程,从而能够在调用流程中触发不同的事件。即使不是在子流程中,也有可能在结束流程后,根据流程的执行情况有多个显示页面供选择。

2.2 转移

转移使用<transition>元素来进行定义,它会作为各种状态元素的子元素。最简单的就是在流程中指定下一个状态:

<transition to="customerReady" />

说明:属性to用于指定流程的下一个状态,如果只使用了to属性,那这个转移就会是当前状态的默认转移选项,如果没有其他可用转移的话,就会使用它。

更常见的转移定义是基于事件的触发来进行的。在视图状态,事件通常会是用户采取的动作。在行为状态,事件是评估表达式得到的结果。而在子流程状态,事件取决于子流程结束状态的ID。在任意事件中,都可以使用on属性来指定触发转移的事件:

<transition on="phoneEntered" to="lookupCustomer" />

说明:在本例中,如果触发了phoneEntered事件,流程将会进入lookupCustomer状态。在抛出异常时,流程也可以进入另一个状态。

<transition to="registrationForm" on-exception=
            "com.springinaction.pizza.service.CustomerNotFoundException" />

2.2.1 全局转移

在创建完流程之后,我们可能会发现有一些状态使用了一些通用的转移。与其在多个状态中都重复通用的转移,不如将其定义为全局转移:

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

说明:定义完这个全局转移后,流程中所有状态都会默认拥有这各cancel转移。

2.3 流程数据

2.3.1 声明变量

流程数据保存在变量中,而变量可以在流程的各个地方进行引用。

<var name="customer" class="com.springinaction.pizza.domain.Customer" />

说明:这里创建了一个新的Customer实例并将其放在名为customer的变量中。这个变量可以在流程的任意状态进行访问。

作为行为状态的一部分或者作为视图状态的入口,可能会使用<evaluate>元素来创建变量如:

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

说明:<evaluate>元素计算了一个表达式,并将结果放到了名为toppingsList的变量中,这个变量是视图作用域的(后面介绍)。类似的,<set>元素也可以设置变量的值:

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

2.3.2 定义流程数据的作用域

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

当使用<var>元素声明变量时,变量始终是流程作用域的,也就是在定义变量的流程内有效。当使用<set><evaluate>的时候,作用域通过nameresult属性的前缀指定。如上面例子中的viewScopeflowScope

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容