一、什么是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">
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>
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; } }
流程定義文件中的第一個狀態也會是流程訪問中的第一個狀態,也可以通過<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">
識別客戶、構造披薩訂單以及支付這樣的活動太復雜了,並不適合將其強行塞入一個狀態,所有將其單獨定義為流程。
流程變量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>
詢問電話號碼:

<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>
隱藏的“_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>
檢查配送區域:
在顧客提供其地址后,我們需要確認他的地址在配送范圍之內。如果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>
如果發送的是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>
使用<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="<br/>"/><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>
當通過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>
在流程進入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('_',' ')); } }
代碼鏈接地址:https://github.com/1977288116/SpringWebFlowDemo.git