第八章 使用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