第八章 使用Spring Web Flow


一、什么是Spring Web Flow?

Spring Web Flow 是一个web框架,它适用于元素按规定流程运行的程序;

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

二、为什么要使用Spring Web Flow?

我们可以使用任何Web框架编写流程化的应用程序,但你会发现流程的定义分散在组成流程的各个元素中,没有地方能够描述整个流程。

三、如何使用Spring Web Flow?

Spring Web Flow是构建于Spring MVC基础之上的。这意味着所有的流程请求都需要经过Spring MVC的DispatcherServlet。我们需要在Spring 应用上下文中配置一些bean来处理流程请求并执行流程。有一些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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
         http://www.springframework.org/schema/webflow-config
          http://www.springframework.org/schema/webflow-config/spring-webflow-config.xsd">
View Code

1、装配流程执行器

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

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

流程执行器负责加载和创建流程,但它并不负责加载流程定义。

2、配置流程注册表

流程注册表的工作是加载流程定义并让流程执行器能够使用它们。

配置方式一:

<!--base-path属性配置流程注册表-->
    <flow:flow-registry id="flowRegistry" base-path="/WEB-INF/flows">
        <flow:flow-location-pattern value="*-flow.xml"/>
    </flow:flow-registry>

流程的ID就是相对于base-path的路径——或者双星号所代表的路径。

配置方式二:

<!--显示声明流程定义文件的位置-->
    <flow:flow-registry id="flowDefinitionRegistry">
        <flow:flow-location path="/WEB-INF/flows/pizza/pizza-flow.xml"/>
    </flow:flow-registry>

3、处理流程请求

配置FlowHandleMapping帮助将流程请求发送给Spring Web Flow

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

FlowHandleMapping装配了流程注册表的引用,这样它就能知道如何将请求的URL匹配到流程上。例如,如果有一个ID为order的流程,FlowHandleMapping就会知道如果请求的URL模式是"/order"的话,就要将其匹配到这个流程上。

配置FlowHandlerAdapter响应请求

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

FlowHandlerAdapter等同于Spring MVC的控制器,它会响应发送的流程请求并对其进行处理。

4、流程的组件

流程是由三个主要元素定义的:状态、转移和流程数据

状态:流程中事件发生的地点;

转移:连接这些点的公路;

数据:流程的当前状况

Spring Web Flow定义了五种不同类型的状态:

行为(Action):行为状态是流程逻辑发生的地方;

决策(Decision):决策状态将流程分成两个方向,它会基于流程数据的评估结果确定流程方向;

结束(End):结束状态是流程的最后一站,一旦进入End状态,流程就会终止;

子流程(Subflow):子流程状态会在当前正在运行的流程上下文中启动一个新的流程;

视图(View):视图状态会暂停流程并邀请用户参与流程;

视图状态:

视图状态用于为用户展现信息并使用户都在流程中发挥作用,实际的试图实现可以是Spring支持的任意视图类型,但通常是用JSP来实现的。

在流程定义的XML文件中,<view-state>用于定义视图状态:

<view-state id="welcome"/>

在这个简单的示例中,流程ID——welcome有两个含义:它在流程内标识这个状态;流程到达这个状态时要展现的逻辑视图名为welcome

如果你愿意指定另外一个视图名,那可以使用view属性做到这一点:

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

如果流程为用户展现了一个表单,你可能希望指明表单所绑定的对象。为了做到这一点,可以设置model属性:

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

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

行为状态:

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

在流程定义的xml文件中,行为状态使用<action-state>元素来声明:

<action-state id="saveOrder">
     <evaluate expression="pizzaFlowActions.saveOrder(order)"/>
     <transition to="thankCustomer"/>
</action-state>

尽管不是严格要求的,但<action-state>元素一般都会有一个<evaluate>作为子元素,<evaluate>元素给出了行为状态要做的事情,expression属性指定了进入这个状态时要评估的表达式。在本例中,给出的expression是SpEL表达式,它表明将会找到ID为pizzaFlowActions的bean并调用其saveOrder()方法。

决策状态:

决策状态能够在流程执行时产生两个分支。

在流程定义的xml中,决策状态通过<decision-state>元素进行定义:

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

子流程状态:

<subflow-state>允许在一个正在执行的流程中调用另一个流程。这类似于在一个方法中调用另一个方法。

可以这样声明:

<!-- 调用顾客子流程 -->
<subflow-state id="order" subflow="pizza/order">
        <input name="order" value="order"/>
        <transition on="customerCreated" to="payment"/>
</subflow-state>

在这里,<input>元素用于传递订单对象作为子流程的输入。如果子流程结束的<end-state>状态ID为customerCreated,那么流程将会转移到名为payment的状态。

结束状态:

在流程定义的xml中,结束状态通过<end-state>元素进行定义:

<end-state id="customerCreated"/>

当流程到达<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="thankCustomer"/>

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

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

<transition on="customerReady" to="buildOrder"/>

在抛出异常时,流程也可以进入另一个状态:

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

全局转移:

<global-transitions>
        <transition on="cancle" to="cancle"/>
</global-transitions>

定义完这个全局转移后,流程中的所有状态都会默认拥有这个cancle转移。

流程数据:

创建变量方式一:

<!--这个变量可以在流程的任意状态进行访问-->
<var name="customer" class="com.springinaction.pizza.domain.Customer"/>

创建变量方式二:

<!--作为行为状态的一部分或者作为视图状态的入口-->
<evaluate result="viewScope.toppingsList"
        expression="T(com.springinaction.pizza.domain.Topping).asList()"/>

在本例中,<evaluate>元素计算了一个表达式(SpEL表达式)并将结果放到了名为toppingsList的变量中,这个变量是视图作用域的。

创建变量方式三:

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

定义流程数据的作用域

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

当使用<var>元素声明变量时,变量始终时流程作用域的,也就是在定义变量的流程内有效。当使用<set>或<evaluate>的时候,作用域通过name或result属性的前缀指定。

5、组合起来:披萨流程

 图中的方框代表了状态而箭头代表了转移。

如下的程序清单展示了使用Spring Web Flow的XML流程定义来实现披萨订单的整体流程:

<?xml version="1.0" encoding="UTF-8"?>
<flow xmlns="http://www.springframework.org/schema/webflow"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://www.springframework.org/schema/webflow http://www.springframework.org/schema/webflow/spring-webflow-2.0.xsd">

    <var name="order" class="com.chenjl.domain.Order"/>

    <!-- 调用顾客子流程 -->
    <subflow-state id="identyfyCustomer" subflow="pizza/customer">
        <output name="customer" value="order.customer"/>
        <transition on="customerReady" to="buildOrder"/>
    </subflow-state>

    <!-- 调用订单子流程 -->
    <subflow-state id="buildOrder" subflow="pizza/order">
        <input name="order" value="order"/>
        <transition on="orderCreated" to="takepayment"/>
    </subflow-state>

    <!-- 调用支付子流程 -->
    <subflow-state id="takePayment" subflow="pizza/payment">
        <input name="order" value="order"/>
        <transition on="paymentTaken" to="saveOrder"/>
    </subflow-state>

    <action-state id="saveOrder">
        <evaluate expression="pizzaFlowActions.saveOrder(order)"/>
        <transition to="thankCustomer"/>
    </action-state>

    <view-state id="thankCustomer">
        <transition to="endState"/>
    </view-state>

    <end-state id="endState"/>

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

</flow>
View Code

Order带有披萨订单的所有细节信息:

package com.chenjl.domain;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

public class Order implements Serializable{
    private static final long serialVersionUID = 1120635411006621502L;

    private Payment payment;

    private List<Pizza> pizzas;

    private Customer customer;

    public Order() {
        pizzas = new ArrayList<Pizza>();
        customer = new Customer();
    }
    public Payment getPayment() {
        return payment;
    }

    public void setPayment(Payment payment) {
        this.payment = payment;
    }

    public List<Pizza> getPizzas() {
        return pizzas;
    }

    public void addPizzas(Pizza pizza) {
        pizzas.add(pizza);
    }

    public Customer getCustomer() {
        return customer;
    }

    public void setCustomer(Customer customer) {
        this.customer = customer;
    }

    public Float getTotal(){
        return 0.00f;
    }
}
View Code

流程定义文件中的第一个状态也会是流程访问中的第一个状态,也可以通过<flow>元素的start-state属性将任意状态指定为开始状态

<?xml version="1.0" encoding="UTF-8"?>
<flow xmlns="http://www.springframework.org/schema/webflow"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://www.springframework.org/schema/webflow
       http://www.springframework.org/schema/webflow/spring-webflow-2.0.xsd"
        start-state="identyfyCustomer">
View Code

识别客户、构造披萨订单以及支付这样的活动太复杂了,并不适合将其强行塞入一个状态,所有将其单独定义为流程。

流程变量order将在前三个状态中进行填充并在第四个状态中进行保存。identifyCustomer子流程状态使用了<output>元素来填充order的customer属性,将其设置为顾客子流程收到的输出。builderOrder和takePayment状态使用了不同的方式,它们使用<input>将order流程变量作为输入,这些子流程就能在内部填充order对象。

在订单得到顾客、一些披萨以及支付细节后,就可以对其进行保存了。saveOrder是处理这个任务的行为状态。它使用<evaluate>来调用ID为pizzaFlowActions的bean的saveOrder()方法,并将保存的订单对象传递进来。订单完成保存后,它会转移到thankCustomer。

thankCustomer状态是一个简单的视图状态,后台使用了"/WEB-INF/flows/pizza/thankCustomer.jsp"这个JSP文件,如下所示:

<html xmlns:jsp="http://java.sun.com/JSP/Page">
    <jsp:output omit-xml-declaration="yes"/>
    <jsp:directive.page contentType="text/html; charset=UTF-8"/>
<head>
    <title>Spizza</title>
</head>
<body>
    <h2>Thank you for your order!</h2>
    <![CDATA[
    <a href="${flowExecutionUrl}&_eventId=finished">Finish</a>
    ]]>
</body>
</html>

在“感谢”页面中,会感谢顾客的订购并为其提供一个完成流程的链接。这个链接是整个页面中最有意思的事情,因为它展示了用户与流程交互的唯一办法。

Spring Web Flow为视图的用户提供了一个flowExecutionUrl变量,它包含了流程的URL。结束链接将一个“_eventId”参数关联到URL上,以便回到web流程时触发finished事件。这个事件将会让流程到达结束状态。

6、收集顾客信息

在每个披萨订单开始前的提问和回答阶段可以用下面的流程图表示:

 

 使用Web流程识别饥饿的披萨顾客:

<?xml version="1.0" encoding="UTF-8"?>
<flow xmlns="http://www.springframework.org/schema/webflow"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://www.springframework.org/schema/webflow http://www.springframework.org/schema/webflow/spring-webflow-2.0.xsd">

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

    <view-state id="welcome">
        <transition on="phoneEntered" to="lookupCustomer"/>
    </view-state>

    <action-state id="lookupCustomer">
        <evaluate result="customer" expression="pizzaFlowActions.lookupCustomer(requestParameters.phoneNumber)"/>
        <transition to="registrationForm" on-exception="com.chenjl.service.CustomerNotFoundException"/>
        <transition to="customerReady"/>
    </action-state>

    <view-state id="registrationForm" model="customer">
        <on-entry>
            <evaluate expression="customer.phoneNumber = requestParameters.phoneNumber"/>
        </on-entry>
        <transition on="submit" to="checkDeliveryArea"/>
    </view-state>

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

    <view-state id="deliveryWarning">
        <transition on="accept" to="addCustomer"/>
    </view-state>

    <action-state id="addCustomer">
        <evaluate expression="pizzaFlowActions.addCustomer(customer)"/>
        <transition to="customerReady"/>
    </action-state>

    <end-state id="cancel"/>

    <end-state id="customerReady">
        <output name="customer"/>
    </end-state>

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

</flow>
View Code

询问电话号码:

<html xmlns:jsp="http://java.sun.com/JSP/Page"
      xmlns:form="http://www.springframework.org/tags/form">
    <jsp:output omit-xml-declaration="yes"/>
    <jsp:directive.page contentType="text/html; UTF-8"/>
    <head><title>Spizza</title></head>
    <body>
        <h2>Welcome to Spizza!!!</h2>
        <form:form>
            <input type="hidden" name="_flowExecutionKey" value="${flowExecutionKey}"/>
            <input type="text" name="phoneNumber"/><br/>
            <input type="submit" name="_eventId_phoneEntered" value="Lookup Customer"/>
        </form:form>
    </body>
</html>
View Code

隐藏的“_flowExecutionkey”输入域,当进入视图状态时,流程暂停并等待用户采取一些行为。赋予视图的流程执行key就是一种返回流程的“回程票”。当用户提交表单时,流程执行key会在“_flowExecutionKey”输入域中返回并在流程暂停的位置进行恢复。

按钮名字的"_eventId_"部分是提供给Spring Web Flow的一个线索,它表明接下来要触发事件。当点击这个按钮提交表单时,会触发phoneEntered事件进而转移到lookupCustomer。

查找顾客:

当欢迎表单提交后,电话号码将从请求参数中抽取出来并传递到pizzaFlowActions bean的lookupCustomer()方法中,并返回customer变量,正常情况会把流程带到customerReady状态,如果找不到顾客,流程被转移到registrationForm状态。

注册新顾客:

registrationForm状态是要求用户填写配送地址的。就像我们之前看到的其他视图状态,它将被渲染成JSP。JSP文件如下所示:

<html   xmlns:c="http://java.sun.com/jsp/jstl/core"
        xmlns:jsp="http://java.sun.com/JSP/Page"
        xmlns:spring="http://www.springframework.org/tags"
      xmlns:form="http://www.springframework.org/tags/form">
<jsp:output omit-xml-declaration="yes"/>
<jsp:directive.page contentType="text/html; UTF-8"/>
<head><title>Spizza</title></head>
<body>
<h2>Customer Registration</h2>
<form:form commandName="customer">
    <input type="hidden" name="_flowExecutionKey" value="${flowExecutionKey}"/>
    <b>Phone number:</b><form:input path="phoneNumber"/><br/>
    <b>Name:</b><form:input path="name"/><br/>
    <b>Address:</b><form:input path="address"/><br/>
    <b>City:</b><form:input path="city"/><br/>
    <b>State:</b><form:input path="state"/><br/>
    <b>Zip Code:</b><form:input path="zipCode"/><br/>
    <input type="submit" name="_eventId_submit" value="Submit"/>
    <input type="submit" name="_eventId_cancel" value="Cancel"/>
</form:form>
</body>
</html>
View Code

检查配送区域:

在顾客提供其地址后,我们需要确认他的地址在配送范围之内。如果Spizza不能派送给他们,那么我们要让顾客知道并建议他们自己到店面里取走披萨。

如果顾客在配送区域内的话,那流程转移到addCustomer状态。否则,顾客被带入到deliveryWarning视图状态。deliveryWarning背后的视图就是"/WEB-INF/flows/pizza/customer/deliveryWarning.jsp",如下所示:

<html xmlns:jsp="http://java.sun.com/JSP/Page">
    <jsp:output omit-xml-declaration="yes"
    <jsp:directive.page contentType="text/html; UTF-8"/>
<head>
    <title>Spizza</title>
</head>
<body>
    <h2>Delivery Unavalilable</h2>
    <p>The address is outside of our delivery area.You may still place the order, but you will need to pick it up yourself.</p>
    <![CDATA[
    <a href="${flowExcecutionUrl}&_eventId=accept">Continue,I'll pick up the order</a> |
    <a href="${flowExcecutionUrl}&_eventId=cancel">Never mind</a>
    ]]>
</body>
</html>
View Code

如果发送的是accept事件,那么流程会转移到addCustomer状态。否则将会转移到全局的cancel结束状态。

存储顾客数据:

当流程抵达addCustomer状态时,用户已经输入了他们的地址。为了将来使用,这个地址需要以某种方式存储起来(可能会存储在数据库中)。addCustomer状态有一个<evaluate>元素,它会调用pizzaFlowActions bean的addCustomer()方法,并将customer流程参数传递进去。一旦这个过程完成,会执行默认的转移,流程将会转移到ID为customerReady的结束状态。

结束流程:

当customer流程走完所有正常路径后,它最终会到达ID为customerReady的结束状态。当调用它的披萨流程恢复时,它会接收到一个customerReady事件,这个事件将使得流程转移到buildOrder状态。

customerReady结束状态包含了一个<output>元素。在流程中这个元素等同于Java中的return语句。它从子流程中传递一些数据到调用流程。

7、构建订单

如下显示了如何将图中所阐述的内容转变成Spring Web Flow定义:

<?xml version="1.0" encoding="UTF-8"?>
<flow xmlns="http://www.springframework.org/schema/webflow"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://www.springframework.org/schema/webflow http://www.springframework.org/schema/webflow/spring-webflow-2.0.xsd">

    <input name="order" required="true"/>
    <view-state id="showOrder">
        <transition on="createPizza" to="createPizza"/>
        <transition on="checkout" to="orderCreated"/>
        <transition on="cancel" to="cancel"/>
    </view-state>

    <view-state id="createPizza" model="flowScope.pizza">
        <on-entry>
            <set name="flowScope.pizza" value="new com.chenjl.domain.Pizza()"/>
            <evaluate result="viewScope.toppingList" expression="T(com.chenjl.domain.Topping).asList()"/>
        </on-entry>
        <transition on="addPizza" to="showOrder">
            <evaluate expression="order.addPizza(flowScope.pizza)"/>
        </transition>
        <transition on="cancel" to="showOrder"/>
    </view-state>

    <end-state id="cancel"/>

    <end-state id="orderCreated"/>

</flow>
View Code

使用<input>元素来将主流程order对象传递进子流程。我们会看到showOrder状态,它是一个基本的视图状态并具有三个不同的转移,分别用于创建披萨、提交订单以及取消订单。

createPizza状态是一个表单视图状态,这个表单可以添加新的Pizza对象到订单中。<on-entry>元素添加了一个新的Pizza对象到流程作用域内,当表单提交时,表单的内容会填充到该对象中。需要注意的是,这个视图状态引用的model是流程作用域内的同一个Pizza对象。Pizza对象将绑定到创建披萨的表单中,如下所示:

<html xmlns:form="http://www.springframework.org/tags/form"
    xmlns:jsp="http://java.sun.com/JSP/Page">
    <jsp:output omit-xml-declaration="yes"/>
    <jsp:directive.page contentType="text/html; UTF-8"/>
<head>
    <title>Spizza</title>
</head>
<body>
    <h2>Create Pizza</h2>
    <form:form commandName="pizza">
        <input type="hidden" name="_flowExecutionKey" value="${flowExecutionKey}"/>
        <b>Size:</b><br/>
        <form:radiobutton path="siez" label="Small (12-inch)" value="SAMLL"/><br/>
        <form:radiobutton path="size" label="Medium (14-inch)" value="MEDIUM"/><br/>
        <form:radiobutton path="size" label="Medium (16-inch)" value="LARGE"/><br/>
        <form:radiobutton path="size" label="Medium (20-inch)" value="GINORMOUS"/><br/>
        <br/>
        <b>Toppings:</b><br/>
        <form:checkboxes path="toppings" items="${toppingList}" delimiter="&lt;br/&gt;"/><br/><br/>
        <input type="submit" class="button" name="_eventId_addPizza" value="Continue"/>
        <input type="submit" class="button" name="_eventId_cancel" value="Cancel"/>
    </form:form>
</body>
</html>
View Code

当通过Continue按钮提交订单时,尺寸和配料选择将会绑定到Pizza对象中并且触发addPizza转移。与这个转移关联的<evaluate>元素表明在转移到showOrder状态之前,流程作用域内的Pizza对象将会传递给订单的addPizza()方法中。

有两种方法来结束这个流程。用户可以点击showOrder视图中的Cancel按钮或者Checkout按钮。这两种操作都会使流程转移到一个<end-state>。但是选择的结束状态id决定了退出这个流程时触发事件,进而最终确定了主流程的下一步行为。主流程要么基于cancel事件要么基于orderCreated事件进行状态转移。在前者情况下,外边的主流程会结束;在后者情况下,它将转移到takePayment子流程,这也是接下来我们要看的。

8、支付

 

 像订单子流程一样,支付子流程也使用<input>元素接收一个Order对象作为输入。

进入支付子流程的时候,用户会到达takePayment状态。这是一个视图状态,在这里用户可以选择使用信用卡、支票或现金进行支付。提交支付信息后,将进入verifyPayment状态。这是一个行为状态,它将校验支付信息是否可以接受。

使用XML定义的支付流程如下所示:

<?xml version="1.0" encoding="UTF-8"?>
<flow xmlns="http://www.springframework.org/schema/webflow"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://www.springframework.org/schema/webflow
       http://www.springframework.org/schema/webflow/spring-webflow-2.0.xsd">

    <input name="order" required="true"/>
    <view-state id="takePayment" model="flowScope.paymentDetails">
        <on-entry>
            <set name="flowScope.paymentDetails" value="new com.chenjl.domain.PaymentDetails()"/>
            <evaluate expression="T(com.chenjl.domain.PaymentType).asList()"/>
        </on-entry>
        <transition on="paymentSubmitted" to="verifyPayment"/>
        <transition on="cancel" to="cancel"/>
    </view-state>
    <action-state id="verifyPayment">
        <evaluate expression="pizzaFlowActions.verifyPayment(flowScope.paymentDetails)"/>
        <transition to="paymentTaken"/>
    </action-state>

    <end-state id="cancel"/>
    <end-state id="paymentTaken"/>
</flow>
View Code

在流程进入takePayment视图时,<on-entry>元素将构建一个支付表单并使用SpEL表达式在流程作用域内创建一个PaymentDetails实例,这是支撑表单的对象。它也会创建视图作用域的paymentTypeList变量,这个变量是一个列表包含了PaymentType枚举的值。在这里,SpEL的T()操作用于获得PaymentType类,这样就可以调用静态的asList()方法。

PaymentType枚举定义如下:

package com.chenjl.domain;

import java.util.Arrays;
import java.util.List;

public enum PaymentType {
    CASH,CHECK,CREDIT_CARD;

    public static List<PaymentType> asList() {
        PaymentType[] all = PaymentType.values();
        return Arrays.asList(all);
    }

    @Override
    public String toString() {
        return capitalizeFully(name().replace('_',' '));
    }

}
View Code

 

代码链接地址:https://github.com/1977288116/SpringWebFlowDemo.git


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM