三喘批、組合起來:披薩流程
這里我們通過訂購披薩的過程對流程進(jìn)行說明饶深。我們首先從構(gòu)建一個高層次的流程開始,它定義了訂購披薩的整體過程朽合。接下來宪彩,我們會將這個流程拆分成子流程尿孔,這些子流程在較低層次定義了細(xì)節(jié)活合。
3.1 定義基本流程
一個新的披薩店決定允許用戶在線訂購以減輕店面電話的壓力芜辕。當(dāng)顧客訪問Pizza
站點時侵续,他們需要進(jìn)行用戶識別状蜗,選擇一個或更多披薩添加到訂單中轧坎,提供支付信息然后提交訂單并等待披薩送過來缸血。如圖所示捎泻。
下面給出實現(xiàn)披薩訂單的整體流程:
<?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.springinaction.pizza.domain.Order"/>
<!-- Customer -->
<subflow-state id="identifyCustomer" subflow="pizza/customer">
<output name="customer" value="order.customer"/>
<transition on="customerReady" to="buildOrder" />
</subflow-state>
<!-- Order -->
<subflow-state id="buildOrder" subflow="pizza/order">
<input name="order" value="order"/>
<transition on="orderCreated" to="takePayment" />
</subflow-state>
<!-- Payment -->
<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 -->
<end-state id="endState" />
<global-transitions>
<transition on="cancel" to="endState" />
</global-transitions>
</flow>
說明:在流程定義中郎汪,首先第一件事就是order
變量的聲明煞赢。每次流程開始的時候照筑,都會創(chuàng)建一個Order
實例朦肘。
package com.springinaction.pizza.domain;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.annotation.Configurable;
@Configurable("order")
public class Order implements Serializable {
private static final long serialVersionUID = 1L;
private Customer customer;//顧客
private List<Pizza> pizzas;//pizza
private Payment payment;//支付詳情
public Order() {
pizzas = new ArrayList<Pizza>();
customer = new Customer();
}
//getter 和setter方法這里省略
}
說明:默認(rèn)情況下,流程定義文件中的第一個狀態(tài)也會是流程訪問中的第一個狀態(tài)咏花。本例中昏翰,也就是identifyCustomer
狀態(tài)(一個子流程)棚菊。也可以通過<flow>
元素的start-state
屬性顯示的指定任意狀態(tài)為開始狀態(tài):
<?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="identifyCustomer">
......
</flow>
說明:
識別顧客统求、構(gòu)造披薩訂單以及支付這樣的活動太復(fù)雜了,并不適合將其強(qiáng)行塞入一個狀態(tài)折剃。后面將其單獨(dú)定義為流程。但是為了更好地整體了解披薩流程己莺,這些活動都是以
<subflow-state>
元素來進(jìn)行展現(xiàn)的凌受。流程變量
order
將在前三個狀態(tài)中進(jìn)行填充并在第四個狀態(tài)中進(jìn)行保存胁艰。identifyCustomer
子流程狀態(tài)使用<output>
元素來填充order
的customer
屬性腾么,將其設(shè)置為顧客子流程收到的輸出解虱。buildOrder
和takePayment
狀態(tài)使用了不同的方式于宙,使用<input>
將order
流程變量作為輸入捞魁,這些子流程就能在其內(nèi)部填充order
對象离咐。在訂單得到顧客昆著、一些披薩和支付細(xì)節(jié)后术陶,
saveOrder
對其進(jìn)行保存接谨,是處理這個任務(wù)的行為狀態(tài)。訂單完成保存后疤坝,會轉(zhuǎn)移到thankCustomer
锅睛,這是一個簡單的視圖狀態(tài)历谍,后臺使用"/WEB-INF/flows/flowspizza/thankCustomer.jsp"
印蔬,如下:
<html>
<head><title>Spring Pizza</title></head>
<body>
<h2>Thank you for your order!</h2>
<![CDATA[
<a href='${flowExecutionUrl}&_eventId=finished'>Finish</a>
]]>
</body>
</html>
說明:在此頁面中侥猬,會感謝顧客的訂購并為其提供一個完成流程的鏈接退唠。這個鏈接展示了用戶與流程交互的唯一辦法瞧预。此處提供了一個flowExecutionUrl
變量垢油,它包含了流程的URL
。結(jié)束鏈接將一個"_eventId"
參數(shù)關(guān)聯(lián)到URL
上运褪,以便回到Web
流程時觸發(fā)finished
事件秸讹。這個事件將會讓流程到達(dá)結(jié)束狀態(tài)。流程將會在結(jié)束狀態(tài)完成劣欢。鑒于在流程結(jié)束后沒有下一步做什么的具體信息,流程將會重新從identifyCustomer
狀態(tài)開始校套,以準(zhǔn)備接受另一個披薩訂單笛匙。
3.2 收集顧客信息
顧客在訂購披薩的時候妹孙,我們需要知道用戶的詳細(xì)信息蠢正,特別是地址信息嚣崭。這里可以根據(jù)電話號碼進(jìn)行查詢(已注冊過的用戶),如果查詢不到葱跋,則需要像用戶詢問源梭。整個流程如下圖所示:
下面對整個流程進(jìn)行定義:
<var name="customer" class="com.springinaction.pizza.domain.Customer" />
<!-- Customer -->
<view-state id="welcome"><!--歡迎顧客-->
<transition on="phoneEntered" to="lookupCustomer"/>
<transition on="cancel" to="cancel"/>
</view-state>
<action-state id="lookupCustomer"><!--查找顧客-->
<evaluate result="customer" expression=
"pizzaFlowActions.lookupCustomer(requestParameters.phoneNumber)" /><!--將結(jié)果填充到customer中-->
<transition to="registrationForm" on-exception=
"com.springinaction.pizza.service.CustomerNotFoundException" /><!--發(fā)生異常后的轉(zhuǎn)移-->
<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" />
<transition on="cancel" to="cancel" />
</view-state>
<decision-state id="checkDeliveryArea"><!--檢查配送地址-->
<if test="pizzaFlowActions.checkDeliveryArea(order.customer.zipCode)"
then="addCustomer"
else="deliveryWarning"/>
</decision-state>
<view-state id="deliveryWarning"><!--顯示配送地址警告-->
<transition on="accept" to="addCustomer" /><!--如果接受自己取披薩,則添加用戶-->
<transition on="cancel" to="cancel" /><!--否則直接取消-->
</view-state>
<action-state id="addCustomer"><!--添加顧客-->
<evaluate expression="pizzaFlowActions.addCustomer(order.customer)" />
<transition to="customerReady" />
</action-state>
<!-- End state -->
<end-state id="cancel" />
<end-state id="customerReady">
<ouput name="customer">
</end-state>
<global-transition>
<transition on="cancel" to="cancel" />
</global-transition>
說明:這里書中有些地方可能是有點問題的油宜。此流程應(yīng)該說是整個訂購流程的第一個分支identifyCustomer
,所以蚁堤,對比來看但狭,這里首先是定義了一個customer
的變量披诗,而最后將customer
進(jìn)行了輸入撬即,這與大流程是相符的。
3.2.1 詢問電話號碼
下面給出歡迎視圖:"/WEB-INF/flows/pizza/customer/welcome.jsp"
:
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<html>
<head><title>Spring Pizza</title></head>
<body>
<h2>Welcome to Spring Pizza!!!</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"
輸入域呈队。當(dāng)進(jìn)入視圖狀態(tài)時剥槐,流程暫停并等待用戶采取一些行為。賦予視圖的流程執(zhí)行key(flow execution key)
就是一種返回流程的“回程票”(claim ticket)
宪摧。當(dāng)用戶提交表單時才沧,流程執(zhí)行key
會在“_flowExecutionKey”
輸入域中返回并在流程暫停的位置進(jìn)行恢復(fù)。同時绍刮,按鈕的名字“_eventId_”
部分是提供給Spring Web Flow
的一個線索,它標(biāo)明了接下來要觸發(fā)的事件。當(dāng)點擊這個按鈕提交表單時,會觸發(fā)phoneEntered
事件進(jìn)而轉(zhuǎn)移到lookupCustomer
。
3.2.2 查找顧客
目前,lookupCustomer()
方法的實現(xiàn)并不重要,這里只需要知道要么返回customer
對象,要么拋出異常。返回的結(jié)果會通過result
屬性設(shè)置到輸入變量customer
中。
3.2.3 注冊新顧客
注冊表單如下:
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<html>
<head><title>Spring Pizza</title></head>
<body>
<h2>Customer Registration</h2>
<form:form commandName="order">
<input type="hidden" name="_flowExecutionKey" value="${flowExecutionKey}"/>
<b>Phone number: </b><form:input path="customer.phoneNumber"/><br/>
<b>Name: </b><form:input path="customer.name"/><br/>
<b>Address: </b><form:input path="customer.address"/><br/>
<b>City: </b><form:input path="customer.city"/><br/>
<b>State: </b><form:input path="customer.state"/><br/>
<b>Zip Code: </b><form:input path="customer.zipCode"/><br/>
<input type="submit" name="_eventId_submit" value="Submit" />
<input type="submit" name="_eventId_cancel" value="Cancel" />
</form:form>
</body>
</html>
3.2.4 檢查配送區(qū)域
在配送表單中需要告知顧客不能將披薩送到它們的地址(就是警告表單deliveryWarning.jsp
):
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head><title>Spring Pizza</title></head>
<body>
<h2>Delivery Unavailable</h2>
<p>The address is outside of our delivery area. The order may still be taken for carry-out.</p>
<a href="${flowExecutionUrl}&_eventId=accept">Accept</a> |
<a href="${flowExecutionUrl}&_eventId=cancel">Cancel</a>
</body>
</html>
說明:這里提供了兩個鏈接逸尖,允許用戶繼續(xù)訂單或者取消苞俘。通過使用與welcome
狀態(tài)相同的flowExecutionUrl
變量岗憋,這些鏈接分別觸發(fā)流程中的accept
或cancel
事件。
3.2.5 存儲顧客數(shù)據(jù)
這里使用addCustomer()
方法對用戶地址等數(shù)據(jù)進(jìn)行保存,并將customer
流程參數(shù)傳遞進(jìn)去廊蜒。
3.2.6 結(jié)束流程
這里給出了兩個流程結(jié)束狀態(tài),而且使用<output>進(jìn)行輸出,這和整個披薩訂購流程是相符合的。
3.3 構(gòu)建訂單
在識別完顧客之后,主流程的下一個事件就是確定它們想要干什么類型的披薩稠肘。訂單子流程就是用于提示用戶創(chuàng)建披薩并將其放入訂單中的拷沸,如圖所示序无。
整個流程較為簡單一也,下面看如何定義此流程:
<input name="order" required="true" />
<!-- Order -->
<view-state id="showOrder"><!-- 展現(xiàn)order的狀態(tài) -->
<transition on="createPizza" to="createPizza" />
<transition on="checkout" to="orderCreated" />
<transition on="cancel" to="cancel" />
</view-state>
<view-state id="createPizza" model="flowScope.pizza"><!-- 創(chuàng)建披薩的狀態(tài)-->
<on-entry>
<set name="flowScope.pizza" value="new com.springinaction.pizza.domain.Pizza()" />
<evaluate result="viewScope.toppingsList"
expression="T(com.springinaction.pizza.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 -->
<end-state id="cancel" />
<end-state id="orderCreated" />
說明:首先是輸入一個order
题诵,這是主流程上創(chuàng)建的Order
對象篷店,這里使用<input>
將主流程的Order
對象進(jìn)行了輸入。createPizza
狀態(tài)的視圖是一個表單讳苦,這個表單可以添加新的Pizza
對象到訂單中袜爪。<on-entry>
元素添加了一個新的Pizza
對象到流程作用域內(nèi)瓢对,當(dāng)表單提交時乙濒,表單的內(nèi)容會填充到該對象中亏掀。需要注意的是,這個視圖狀態(tài)引用的model
是流程作用域內(nèi)的同一個Pizza
對象(都是flowScope.pizza
)宇智。
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<div>
<h2>Create Pizza</h2>
<form:form commandName="pizza">
<input type="hidden" name="_flowExecutionKey" value="${flowExecutionKey}"/>
<b>Size: </b><br/>
<form:radiobutton path="size" label="Small (12-inch)" value="SMALL"/><br/>
<form:radiobutton path="size" label="Medium (14-inch)" value="MEDIUM"/><br/>
<form:radiobutton path="size" label="Large (16-inch)" value="LARGE"/><br/>
<form:radiobutton path="size" label="Ginormous (20-inch)" value="GINORMOUS"/><br/>
<b>Toppings: </b><br/>
<form:checkboxes path="toppings" items="${toppingsList}" 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>
</div>
說明:這里對于showOrder.jsp
就不給出了甘萧。當(dāng)通過Continue
按鈕提交時怪得,相關(guān)數(shù)據(jù)會綁定到Pizza
對象中且觸發(fā)addPizza
轉(zhuǎn)移硝拧。
3.4 支付
支付流程較為簡單,首先是輸入相關(guān)的支付信息,然后提交吓揪。具體流程如圖所示亲怠。
下面給出流程定義:
<input name="order" required="true"/>
<view-state id="takePayment" model="flowScope.paymentDetails">
<on-entry>
<set name="flowScope.paymentDetails"
value="new com.springinaction.pizza.domain.PaymentDetails()" />
<evaluate result="viewScope.paymentTypeList"
expression="T(com.springinaction.pizza.domain.PaymentType).asList()" />
</on-entry>
<transition on="paymentSubmitted" to="verifyPayment" />
<transition on="cancel" to="cancel" />
</view-state>
<action-state id="verifyPayment">
<evaluate result="order.payment" expression=
"pizzaFlowActions.verifyPayment(flowScope.paymentDetails)" />
<transition to="paymentTaken" />
</action-state>
<!-- End state -->
<end-state id="cancel" />
<end-state id="paymentTaken" />
說明:對于選擇支付信息的頁面這里就不給出了。這里在進(jìn)入視圖時柠辞,<on-entry>
元素構(gòu)建了一個支付表單并創(chuàng)建了一個PaymentDetails
實例团秽。之后還創(chuàng)建了視圖作用域的paymentTypeList
變量,這個變量是一個列表包含了PaymentType
枚舉的值叭首。這里需要調(diào)用一個adList()
方法习勤。
package com.springinaction.pizza.domain;
import java.util.Arrays;
import java.util.List;
import org.apache.commons.lang3.text.WordUtils;
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 WordUtils.capitalizeFully(name().replace('_', ' '));
}
}
說明:至此,整個流程就執(zhí)行完了焙格。