20张峰、使用Spring Web Flow(2)(Spring筆記)

三喘批、組合起來:披薩流程

這里我們通過訂購披薩的過程對流程進(jìn)行說明饶深。我們首先從構(gòu)建一個高層次的流程開始,它定義了訂購披薩的整體過程朽合。接下來宪彩,我們會將這個流程拆分成子流程尿孔,這些子流程在較低層次定義了細(xì)節(jié)活合。

3.1 定義基本流程

一個新的披薩店決定允許用戶在線訂購以減輕店面電話的壓力芜辕。當(dāng)顧客訪問Pizza站點時侵续,他們需要進(jìn)行用戶識別状蜗,選擇一個或更多披薩添加到訂單中轧坎,提供支付信息然后提交訂單并等待披薩送過來缸血。如圖所示捎泻。

1

下面給出實現(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>元素來填充ordercustomer屬性腾么,將其設(shè)置為顧客子流程收到的輸出解虱。buildOrdertakePayment狀態(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)行查詢(已注冊過的用戶),如果查詢不到葱跋,則需要像用戶詢問源梭。整個流程如下圖所示:


2

下面對整個流程進(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ā)流程中的acceptcancel事件。

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)建披薩并將其放入訂單中的拷沸,如圖所示序无。


3

整個流程較為簡單一也,下面看如何定義此流程:

<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)的支付信息,然后提交吓揪。具體流程如圖所示亲怠。


4

下面給出流程定義:

<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í)行完了焙格。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末图毕,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子眷唉,更是在濱河造成了極大的恐慌予颤,老刑警劉巖损肛,帶你破解...
    沈念sama閱讀 212,454評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異荣瑟,居然都是意外死亡治拿,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評論 3 385
  • 文/潘曉璐 我一進(jìn)店門笆焰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來劫谅,“玉大人,你說我怎么就攤上這事嚷掠∧蠹欤” “怎么了?”我有些...
    開封第一講書人閱讀 157,921評論 0 348
  • 文/不壞的土叔 我叫張陵不皆,是天一觀的道長贯城。 經(jīng)常有香客問我,道長霹娄,這世上最難降的妖魔是什么能犯? 我笑而不...
    開封第一講書人閱讀 56,648評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮犬耻,結(jié)果婚禮上踩晶,老公的妹妹穿的比我還像新娘。我一直安慰自己枕磁,他們只是感情好渡蜻,可當(dāng)我...
    茶點故事閱讀 65,770評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著计济,像睡著了一般茸苇。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上沦寂,一...
    開封第一講書人閱讀 49,950評論 1 291
  • 那天学密,我揣著相機(jī)與錄音,去河邊找鬼凑队。 笑死则果,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的漩氨。 我是一名探鬼主播西壮,決...
    沈念sama閱讀 39,090評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼叫惊!你這毒婦竟也來了款青?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,817評論 0 268
  • 序言:老撾萬榮一對情侶失蹤霍狰,失蹤者是張志新(化名)和其女友劉穎抡草,沒想到半個月后饰及,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,275評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡康震,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,592評論 2 327
  • 正文 我和宋清朗相戀三年燎含,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片腿短。...
    茶點故事閱讀 38,724評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡屏箍,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出橘忱,到底是詐尸還是另有隱情赴魁,我是刑警寧澤,帶...
    沈念sama閱讀 34,409評論 4 333
  • 正文 年R本政府宣布钝诚,位于F島的核電站颖御,受9級特大地震影響哄陶,放射性物質(zhì)發(fā)生泄漏捐名。R本人自食惡果不足惜堵泽,卻給世界環(huán)境...
    茶點故事閱讀 40,052評論 3 316
  • 文/蒙蒙 一史飞、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧雕擂,春花似錦缘挽、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽杠茬。三九已至月褥,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間瓢喉,已是汗流浹背宁赤。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留栓票,地道東北人决左。 一個月前我還...
    沈念sama閱讀 46,503評論 2 361
  • 正文 我出身青樓,卻偏偏與公主長得像走贪,于是被迫代替她去往敵國和親佛猛。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,627評論 2 350

推薦閱讀更多精彩內(nèi)容