《Spring實(shí)戰(zhàn)》學(xué)習(xí)筆記-第八章:使用Spring Web Flow

第四版的第八章內(nèi)容與第三版基本一致。

本章內(nèi)容:

  • 創(chuàng)建會(huì)話式web應(yīng)用程序
  • 定義流程狀態(tài)和行為
  • 保護(hù)web流程

互聯(lián)網(wǎng)的一個(gè)奇特之處就在于它很容易讓人迷失崎场。有如此多的內(nèi)容可以查看和閱讀见秤,而超鏈接是其強(qiáng)大魔力的核心所在砂竖。

有時(shí)候,web應(yīng)用程序需要控制web沖浪者的導(dǎo)向鹃答,引導(dǎo)他們一步步地訪問應(yīng)用乎澄。比如電子商務(wù)網(wǎng)站的付款流程,從購物車開始测摔,應(yīng)用程序會(huì)引導(dǎo)你依次經(jīng)過配送詳情置济、賬單信息以及最終的訂單確認(rèn)。

Spring Web Flow是一個(gè)web框架锋八,它適用于元素規(guī)定流程運(yùn)行的程序浙于。本章中,我們將會(huì)探索它是如何用于Spring Web框架平臺(tái)的挟纱。

其實(shí)我們可以使用任何的Web框架編寫流程化的應(yīng)用程序路媚,比如使用Struts構(gòu)建特定的流程。但是這樣沒有辦法將流程與實(shí)現(xiàn)分開樊销,你會(huì)發(fā)現(xiàn)流程的定義分散在組成流程的各個(gè)元素中整慎,沒有特定的地方能夠完整地描述整個(gè)流程。

Spring Web Flow是Spring MVC的擴(kuò)展围苫,它支持開發(fā)基于流程的應(yīng)用程序裤园,可以將流程的定義和實(shí)現(xiàn)流程行為的類和視圖分離開來。

在介紹Spring Web Flow的時(shí)候剂府,我們會(huì)暫且放下Spittr樣例拧揽,而使用生產(chǎn)披薩訂單的web程序。

使用的第一步是在項(xiàng)目中進(jìn)行安裝,那么就從安裝開始吧淤袜。

在Spring中配置Spring Web Flow

Spring Web Flow是基于Spring MVC構(gòu)建的痒谴,這就意味著所有的流程請(qǐng)求都需要經(jīng)過Spring MVC的DispatcherServlet。我們需要在Spring應(yīng)用上下文中配置一些Bean來處理流程請(qǐng)求并執(zhí)行流程铡羡。

現(xiàn)在還沒有支持使用Java來配置Spring Web Flow积蔚,所以沒得選,只能在XML中進(jìn)行配置烦周。有一些Bean會(huì)使用Spring Web Flow的Spring配置文件命名空間來進(jìn)行聲明尽爆,因此我們需要在上下文定義XML文件中添加相應(yīng)的命名空間:

<?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"
    xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/webflow-config 
   http://www.springframework.org/schema/webflow-config/spring-webflow-config-2.3.xsd
   http://www.springframework.org/schema/beans 
   http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
   http://www.springframework.org/schema/context 
   http://www.springframework.org/schema/context/spring-context-3.0.xsd">

聲明了命名空間后,就可以準(zhǔn)備裝配Web Flow的Bean了读慎。

編寫流程執(zhí)行器

顧名思義漱贱,流程執(zhí)行器(flow executor )就是用來驅(qū)動(dòng)流程的執(zhí)行。當(dāng)用戶進(jìn)入到一個(gè)流程時(shí)夭委,流程執(zhí)行器會(huì)為該用戶創(chuàng)建并啟動(dòng)一個(gè)流程執(zhí)行器的實(shí)例幅狮。當(dāng)流程暫停時(shí)(例如為用戶展示視圖時(shí)),流程執(zhí)行器會(huì)在用戶執(zhí)行操作后恢復(fù)流程株灸。

在Spring中彪笼,<flow:flow-executor>元素可以創(chuàng)建一個(gè)流程執(zhí)行器:
<flow:flow-executor id="flowExecutor" />

盡管流程執(zhí)行器負(fù)責(zé)創(chuàng)建和執(zhí)行流程,但它并不負(fù)責(zé)加載流程定義蚂且。這個(gè)要由流程注冊(cè)表(flow registry)負(fù)責(zé)配猫,下面會(huì)創(chuàng)建它。

配置流程注冊(cè)表

流程注冊(cè)表的工作就是加載流程定義杏死,并讓流程執(zhí)行器可以使用它們泵肄。可以在Spring中使用<flow:flow-registry>進(jìn)行配置:

<flow:flow-registry id="flowRegistry" base-path="/WEB-INF/flows">
    <flow:flow-location-pattern value="/**/*-flow.xml" />
</flow:flow-registry>

正如這里聲明的淑翼,流程注冊(cè)表會(huì)在/WEB-INF/flows目錄下尋找流程定義腐巢,這個(gè)路徑是由base-path屬性指明的。根據(jù)<flow:flow-location-pattern>元素玄括,任何以-flow.xml結(jié)尾的XML文件都會(huì)被視為流程定義冯丙。

所有的流程都是通過其ID來進(jìn)行引用的。使用<flow:flow-location-pattern>元素遭京,流程的ID就是相對(duì)于base-path的路徑胃惜,或者是雙星號(hào)所代表的路徑,如下圖展示了流程ID是如何計(jì)算的:

在使用流程定位模式時(shí)哪雕,流程定義文件相對(duì)于基本路徑的路徑將用作流程的id
在使用流程定位模式時(shí)船殉,流程定義文件相對(duì)于基本路徑的路徑將用作流程的id

另外,你也可以不使用base-path屬性斯嚎,直接顯式地聲明流程定義文件的位置:

<flow:flow-registry id="flowRegistry">
    <flow:flow-location path="/WEB-INF/flows/springpizza.xml" />
</flow:flow-registry>

這里使用了<flow:flow-location>而不是<flow:flow-location-pattern>利虫,path屬性直接指定了/WEB-INF/flows/springpizza.xml為流程定義文件挨厚。當(dāng)這樣定義時(shí),流程的ID是從流程定義文件的文件名中獲取的糠惫,這就是springpizza疫剃。

如果你希望更顯示地指定流程ID,那么可以通過<flow:flow-location>元素的id屬性來進(jìn)行設(shè)置硼讽。例如巢价,要設(shè)定pizza作為流程ID,可以這樣進(jìn)行配置:

<flow:flow-registry id="flowRegistry">
    <flow:flow-location id="pizza"
        path="/WEB-INF/flows/springpizza.xml" />
</flow:flow-registry>

處理流程請(qǐng)求

正如前面的章節(jié)中提到的理郑,DispatcherServlet會(huì)將請(qǐng)求分發(fā)給控制器蹄溉,但是對(duì)于流程而言咨油,你需要FlowHandlerMapping來幫助DispatcherServlet將流程請(qǐng)求發(fā)送給Spring Web Flow您炉。FlowHandlerMapping的配置如下:

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

FlowHandlerMapping裝配了注冊(cè)表的引用,這樣它就知道如何將請(qǐng)求的URL匹配到流程上役电。例如赚爵,如果有一個(gè)ID為pizza的流程,FlowHandlerMapping就會(huì)知道如果請(qǐng)求的URL是/pizza的話法瑟,就會(huì)將其匹配到這個(gè)流程上冀膝。

然而,FlowHandlerMapping的工作僅僅是將流程請(qǐng)求定向到Spring Web Flow霎挟,響應(yīng)請(qǐng)求的是FlowHandlerAdapter窝剖,它等同于Spring MVC的控制器,會(huì)對(duì)流程請(qǐng)求進(jìn)行響應(yīng)并處理酥夭。FlowHandlerAdapter可以像下面這樣裝配成一個(gè)Spring Bean:

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

這個(gè)處理適配器就是DispatcherServlet和Spring Web Flow之間的橋梁赐纱。它會(huì)處理流程請(qǐng)求并管理基于這些請(qǐng)求的流程。在這里熬北,它裝配了流程執(zhí)行器的引用疙描,而后者是為請(qǐng)求執(zhí)行流程的。

現(xiàn)在已經(jīng)配置了Spring Web Flow所需的Bean和組件讶隐,下面所需的就是真正的定義流程了起胰。首先了解下流程的組成元素。

流程組件

在Spring Web Flow中巫延,流程是由3個(gè)主要元素組成的:狀態(tài)(state)效五、轉(zhuǎn)移(transition)和流程數(shù)據(jù)(flow data)。狀態(tài)是流程中事件發(fā)生的地點(diǎn)炉峰。如果將流程想象成公路旅行火俄,那么狀態(tài)就是路途上的城鎮(zhèn)、路邊飯店以及風(fēng)景點(diǎn)等讲冠。流程中的狀態(tài)是業(yè)務(wù)邏輯執(zhí)行瓜客、做出決策或?qū)㈨撁嬲故窘o用戶的地方,而不是在公路旅行中買薯片或者可樂這些行為。

如果說流程狀態(tài)是公路上停下來的地點(diǎn)谱仪,那么轉(zhuǎn)移就是連接這些點(diǎn)的公路玻熙。在流程上,需要通過轉(zhuǎn)移從一個(gè)狀態(tài)到達(dá)另一個(gè)狀態(tài)疯攒。

在城鎮(zhèn)間旅行的時(shí)候嗦随,可能需要購買一些紀(jì)念品、留下一下回憶敬尺。類似的枚尼,在流程處理過程中,它要收集一些數(shù)據(jù):流程當(dāng)前狀況等砂吞。也許你很想將其稱為流程的狀態(tài)署恍,但是我們定義的狀態(tài)已經(jīng)有了另外的含義。

狀態(tài)

Spring Web Flow定義了5種不同的狀態(tài)蜻直,如下表所示盯质。通過選擇Spring Web Flow的狀態(tài)幾乎可以把任意的安排功能構(gòu)造成會(huì)話式的Web應(yīng)用程序。盡管并不是所有的流程都需要下表中的狀態(tài)概而,但最終你可能會(huì)經(jīng)常使用其中幾個(gè)呼巷。

狀態(tài)類型 作用
行為(Action) 流程邏輯發(fā)生的地方
決策(Decision) 決策狀態(tài)將流程分為兩個(gè)方向,它會(huì)基于流程數(shù)據(jù)的評(píng)估結(jié)果確定流程方向
結(jié)束(End) 結(jié)束狀態(tài)是流程的最后一站赎瑰,進(jìn)入End狀態(tài)王悍,流程就會(huì)終止
子流程(Subflow) 子流程狀態(tài)會(huì)在當(dāng)前正在運(yùn)行的流程上下文中啟動(dòng)一個(gè)新的流程
視圖(View) 視圖狀態(tài)會(huì)暫停流程并邀請(qǐng)用戶參與流程

首先了解下這些流程元素在Spring Web Flow定義中是如何表現(xiàn)的。

視圖狀態(tài)

視圖狀態(tài)用來為用戶展現(xiàn)信息并使用戶在流程中發(fā)揮作用餐曼。實(shí)際的視圖實(shí)現(xiàn)可以是Spring支持的任意視圖類型压储,但通常是用JSP來實(shí)現(xiàn)的。

在流程定義文件中晋辆,<view-state>用來定義視圖狀態(tài):

<view-state id="welcome" />

在這個(gè)簡單的示例中渠脉,id屬性有兩個(gè)含義。其一瓶佳,它定義了流程中的狀態(tài)芋膘。其二,因?yàn)檫@里沒有其他地方指定視圖霸饲,那么它就指定了流程到達(dá)這個(gè)狀態(tài)時(shí)要展現(xiàn)的邏輯視圖名稱為welcome为朋。

如果要顯示地指定另外一個(gè)視圖名稱,那么就可以使用view屬性:

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

如果流程為用戶展現(xiàn)了一個(gè)表單厚脉,你希望指定表單所綁定的對(duì)象习寸,可以使用model屬性:

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

這里指定了takePayment視圖將綁定流程范圍內(nèi)的paymentDetails對(duì)象。

行為狀態(tài)

視圖狀態(tài)包括流程應(yīng)用的用戶傻工,而行為狀態(tài)則是應(yīng)用程序自身在執(zhí)行任務(wù)霞溪。行為狀態(tài)一般會(huì)觸發(fā)Spring所管理Bean的一些方法孵滞,并跟你講方法調(diào)用的執(zhí)行結(jié)果轉(zhuǎn)移到另一個(gè)狀態(tài)。

在流程定義文件中鸯匹,行為狀態(tài)使用<action-state>元素來聲明:

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

盡管沒有嚴(yán)格要求坊饶,但是<action-state>元素一般都有一個(gè)<evaluate>子元素,該元素給出了行為狀態(tài)要做的事情殴蓬,expression屬性指定了進(jìn)入這個(gè)狀態(tài)時(shí)要評(píng)估的表達(dá)式匿级。本例中,給出的是SpEL表達(dá)式染厅,這表明它將會(huì)找到ID為pizzaFlowActions的Bean痘绎,并調(diào)用其saveOrder()方法。

決策狀態(tài)

流程有可能會(huì)按照線性執(zhí)行下去肖粮,從一個(gè)狀態(tài)到另一個(gè)狀態(tài)孤页,沒有其他的替代路線。但是更常見的是流程在某一個(gè)點(diǎn)根據(jù)流程當(dāng)前情況進(jìn)入不同的分支尿赚。

決策狀態(tài)能夠使得在流程執(zhí)行時(shí)產(chǎn)生兩個(gè)分支散庶,它會(huì)評(píng)估一個(gè)Boolean表達(dá)式蕉堰,根據(jù)結(jié)果是true還是false在兩個(gè)狀態(tài)轉(zhuǎn)移中選擇一個(gè)凌净。在流程定義文件中,使用<decision-state>元素來定義決策狀態(tài):

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

<decision-state>并不是單獨(dú)工作的屋讶,<if>元素是其核心冰寻,它是進(jìn)行表達(dá)式評(píng)估的地方,如果表達(dá)式結(jié)果為true皿渗,流程會(huì)轉(zhuǎn)向then屬性指定的狀態(tài)斩芭,為false會(huì)轉(zhuǎn)向else指定的狀態(tài)中。

子流程狀態(tài)

也許你不會(huì)將應(yīng)用程序的所有邏輯都寫在一個(gè)方法里乐疆,而是將其分散到多個(gè)類划乖、方法一起其他結(jié)構(gòu)中。

同樣的挤土,將流程分成獨(dú)立的部分也是個(gè)不錯(cuò)的主意琴庵。<subflow-state>元素允許在一個(gè)正在執(zhí)行的流程中調(diào)用另一個(gè)流程:

<subflow-state id="order" subflow="pizza/order">
    <input name="order" value="order"/>
    <transition on="orderCreated" to="payment" />
</subflow-state>

這里,<input>元素作為子流程的輸入被用于傳遞訂單對(duì)象仰美。如果子流程結(jié)束的<end-state>狀態(tài)ID為orderCreated迷殿,那么本流程就會(huì)轉(zhuǎn)移到ID為payment的狀態(tài)。

結(jié)束狀態(tài)

最后咖杂,所有的流程都要結(jié)束庆寺。這就是流程轉(zhuǎn)移到結(jié)束狀態(tài)時(shí)所做的。<end-state>元素指定了流程的結(jié)束:

<end-state id="customerReady" />

當(dāng)流程到達(dá)<end-state>時(shí)诉字,流程就會(huì)結(jié)束懦尝。接下來發(fā)生什么要取決于以下幾個(gè)因素:

  • 如果結(jié)束的流程是個(gè)子流程知纷,那么調(diào)用它的流程將會(huì)從<subflow-state>處繼續(xù)執(zhí)行。<end-state>的ID將會(huì)用作時(shí)間觸發(fā)從<subflow-state>開始的轉(zhuǎn)移陵霉。
  • 如果<end-state>設(shè)置了view屬性屈扎,那么就會(huì)渲染指定的視圖。視圖可以是相對(duì)于流程的路徑撩匕,也可以是流程模板鹰晨,使用externalRedirect:前綴的會(huì)重定向到流程外部的頁面,而使用flowRedirect:前綴的則會(huì)重定向到另外一個(gè)流程止毕。
  • 如果結(jié)束的流程不是子流程也沒有配置view屬性模蜡,那么這個(gè)流程就會(huì)結(jié)束。瀏覽器最后將會(huì)加載流程的基本URL地址扁凛,同時(shí)忍疾,因?yàn)闆]有活動(dòng)的流程,所以會(huì)開始一個(gè)新的流程實(shí)例谨朝。

需要注意的是一個(gè)流程可能有多個(gè)結(jié)束狀態(tài)卤妒。因?yàn)樽恿鞒痰慕Y(jié)束狀態(tài)ID確定了激活的事件,所以也許你會(huì)希望以多種結(jié)束狀態(tài)來結(jié)束子流程字币,從而能夠在調(diào)用流程中觸發(fā)不同的事件则披,即使不是在子流程中,也有可能在結(jié)束流程后洗出,根據(jù)流程的執(zhí)行情況有多個(gè)顯示頁面供選擇士复。

下面看一下流程是如何在狀態(tài)間遷移的,如何在流程中通過定義轉(zhuǎn)移來完成道路鋪設(shè)翩活。

轉(zhuǎn)移

如前文所述阱洪,轉(zhuǎn)移連接了流程中的狀態(tài)。流程中除結(jié)束狀態(tài)外的每個(gè)狀態(tài)菠镇,至少需要一個(gè)轉(zhuǎn)移冗荸,這樣就知道在狀態(tài)完成時(shí)的走向。一個(gè)狀態(tài)也許有多個(gè)轉(zhuǎn)移利耍,分別表示當(dāng)前狀態(tài)結(jié)束時(shí)可以執(zhí)行的不同路徑蚌本。

轉(zhuǎn)移是通過<transition>元素來定義的,作為其他狀態(tài)元素(<action-state>堂竟、<view-state><subflow-state>)的子元素魂毁。最簡單的形式就是<transition>元素在流程中指定下一個(gè)狀態(tài):

<transition to="customerReady" />

屬性to用于指定流程中的下一個(gè)狀態(tài)。如果<transition>元素只使用了to屬性出嘹,那么這個(gè)轉(zhuǎn)移就會(huì)是當(dāng)前狀態(tài)的默認(rèn)轉(zhuǎn)移選項(xiàng)席楚,如果沒有其他可用轉(zhuǎn)移的話,就會(huì)使用它税稼。

更為常見的轉(zhuǎn)移定義是基于事件的觸發(fā)來進(jìn)行的烦秩。在視圖狀態(tài)垮斯,事件通常會(huì)是用戶采取的動(dòng)作。在行為狀態(tài)只祠,事件是評(píng)估表達(dá)式得到的結(jié)果兜蠕。而在子流程狀態(tài),事件取決于子流程結(jié)束狀態(tài)的ID抛寝。在任意事件中熊杨,你可以使用on屬性來指定觸發(fā)轉(zhuǎn)移的事件:

<transition on="phoneEntered" to="lookupCustomer"/>

在示例中,如果觸發(fā)了phoneEntered事件流程盗舰,就會(huì)進(jìn)入lookupCustomer狀態(tài)晶府。

在拋出異常時(shí),流程也可能進(jìn)入另一種狀態(tài)钻趋。例如川陆,如果沒有找到顧客的記錄,你可能希望流程轉(zhuǎn)移到一個(gè)顯示注冊(cè)表單的視圖狀態(tài)蛮位,如下面:

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

屬性on-exception和屬性on十分類似较沪,它是指定了要發(fā)生轉(zhuǎn)移的異常而不是一個(gè)事件。

全局轉(zhuǎn)移

在創(chuàng)建完流程后失仁,也許你會(huì)發(fā)現(xiàn)有些狀態(tài)使用了一些通用的轉(zhuǎn)移尸曼。例如在整個(gè)流程中到處都有如下轉(zhuǎn)移:

<transition on="cancel" to="endState" />

與其在多個(gè)流程狀態(tài)中重復(fù)通用的轉(zhuǎn)移,不如將其作為<globaltransitions>的子元素陶因,從而作為全局轉(zhuǎn)移骡苞。

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

定義完全局轉(zhuǎn)移垂蜗,流程中所有的狀態(tài)都會(huì)默認(rèn)擁有這個(gè)cancel轉(zhuǎn)移楷扬。

流程數(shù)據(jù)

當(dāng)流程從一個(gè)狀態(tài)到達(dá)另一個(gè)狀態(tài)時(shí),它會(huì)帶走一些數(shù)據(jù)贴见。有時(shí)這些數(shù)據(jù)很快就會(huì)被使用烘苹,比如直接展示給用戶,有時(shí)這些數(shù)據(jù)需要在整個(gè)流程中傳遞并在流程結(jié)束時(shí)使用片部。

聲明變量

流程數(shù)據(jù)是保存在變量中的镣衡,而變量可以在流程的任意位置進(jìn)行引用,并且可以以多種方式進(jìn)行創(chuàng)建档悠。其中最簡單的方式就是使用<var>元素:

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

這里創(chuàng)建了一個(gè)新的Customer實(shí)例并將其放在customer變量中廊鸥,這個(gè)變量可以在流程的任意狀態(tài)下進(jìn)行訪問使用。

作為行為狀態(tài)的一部分或者說作為視圖狀態(tài)的入口辖所,也可以使用<evaluate>元素來創(chuàng)建變量:

<evaluate result="viewScope.toppingsList"
    expression="T(com.springinaction.pizza.domain.Topping).asList()" />

這里<evaluate>元素計(jì)算了一個(gè)SpEL表達(dá)式惰说,并將結(jié)果放到toppingsList變量中,這個(gè)變量是視圖作用域的缘回。

類似的吆视,<set>元素也可以設(shè)置變量的值:

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

<set>元素與<evaluate>元素類似典挑,都是講變量設(shè)置為表達(dá)式計(jì)算的結(jié)果。這里我們?cè)O(shè)置了一個(gè)流程范圍的pizza變量啦吧,它的值為Pizza對(duì)象的新實(shí)例您觉。

流程數(shù)據(jù)的作用域

流程中所攜帶的數(shù)據(jù)都有其各自的生命周期,這取決于保存數(shù)據(jù)的變量本身的作用域授滓,如下表:

范圍 生命周期
Conversation 最高層級(jí)的流程開始時(shí)創(chuàng)建琳水,在最高層級(jí)的流程結(jié)束時(shí)銷毀。由最高層級(jí)的流程和其所有的子流程所共享
Flow 當(dāng)流程開始時(shí)創(chuàng)建般堆,在流程結(jié)束時(shí)銷毀炫刷。只在創(chuàng)建它的流程中是可見的
Request 當(dāng)一個(gè)請(qǐng)求進(jìn)入流程時(shí)創(chuàng)建,流程返回時(shí)銷毀
Flash 流程開始時(shí)創(chuàng)建郁妈,流程結(jié)束時(shí)銷毀浑玛。在視圖狀態(tài)解析后,才會(huì)被清除
View 進(jìn)入視圖狀態(tài)時(shí)創(chuàng)建噩咪,退出這個(gè)狀態(tài)時(shí)銷毀顾彰,只在視圖狀態(tài)內(nèi)可見

當(dāng)使用<var>元素聲明變量時(shí),變量始終是流程作用域的胃碾,也就是在流程作用域內(nèi)定義變量涨享。當(dāng)使用<set><evaluate>時(shí),作用域通過name或result屬性的前綴指定仆百。例如厕隧,將一個(gè)值賦給流程作用域的theAnswer變量:

<set name="flowScope.theAnswer" value="42"/>

到目前為止,我們已經(jīng)看到了Web流程的所有原材料俄周,下面要將其進(jìn)行整合了吁讨,完成一個(gè)完整的流程。

組合起來:披薩流程

首先從構(gòu)建一個(gè)高層次的流程開始峦朗,它定義了訂購披薩的整體流程建丧,然后將其拆分為多個(gè)子流程。

定義基本流程

當(dāng)顧客訪問Spizza網(wǎng)站時(shí)波势,他們需要進(jìn)行用戶識(shí)別翎朱、選擇一個(gè)或多個(gè)披薩添加到訂單、提供支付信息尺铣,然后提交訂單拴曲,等待披薩上來,如下圖:


網(wǎng)上購買披薩的流程
網(wǎng)上購買披薩的流程

下面展示Spring Web Flow的XML流程定義來實(shí)現(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.3.xsd">
    <var name="order" class="com.springinaction.pizza.domain.Order" />
    <!-- 調(diào)用顧客子流程 -->
    <subflow-state id="identifyCustomer" subflow="pizza/customer">
        <output name="customer" value="order.customer" />
        <transition on="customerReady" to="buildOrder" />
    </subflow-state>
    <!-- 調(diào)用訂單子流程 -->
    <subflow-state id="buildOrder" subflow="pizza/order">
        <input name="order" value="order" />
        <transition on="orderCreated" to="takePayment" />
    </subflow-state>
    <!-- 調(diào)用支付子流程 -->
    <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" />
    <!-- 全局取消轉(zhuǎn)移 -->
    <global-transitions>
        <transition on="cancel" to="endState" />
    </global-transitions>
</flow>

流程定義中的第一件事就是聲明order變量凛忿。每次流程開始的時(shí)候都會(huì)創(chuàng)建一個(gè)Order實(shí)例澈灼。Order類會(huì)包含關(guān)于訂單的所有信息、顧客信息侄非、訂購的披薩以及支付信息等蕉汪。

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;
   private Payment payment;

   public Order() {
      pizzas = new ArrayList<Pizza>();
      customer = new Customer();
   }
   
   //getters and setters
}   

流程定義的主要組成部分是流程的狀態(tài)流译,默認(rèn)情況下,流程定義文件中的第一個(gè)狀態(tài)會(huì)是流程訪問的第一個(gè)狀態(tài)者疤。本例中就是identifyCustomer狀態(tài)(一個(gè)子流程)福澡。也可以通過<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.3.xsd"
    start-state="identifyCustomer">
    ...
</flow>

識(shí)別顧客、構(gòu)建披薩訂單和支付這樣的活動(dòng)比較復(fù)雜驹马,并不適合將其直接放在一個(gè)狀態(tài)革砸,而是以<subflow-state>元素展現(xiàn)的。

流程變量order將在前3個(gè)狀態(tài)中進(jìn)行填充并在第4個(gè)狀態(tài)中進(jìn)行保存糯累。identifyCustomer子流程使用了<output>元素來填充order的customer屬性算利,將其設(shè)置為調(diào)用顧客子流程收到的輸出。buildOrder和takePayment狀態(tài)使用了不同的方式泳姐,它們使用<input>將order流程變量作為輸入效拭,這些子流程就能在其內(nèi)部填充order對(duì)象。

在訂單得到顧客胖秒、披薩以及支付信息后缎患,就可以對(duì)其進(jìn)行保存。saveOrder是處理這個(gè)任務(wù)的行為狀態(tài)阎肝。它使用<evaluate>來調(diào)用ID為pizzaFlowActions的Bean的saveOrder()方法挤渔,并將保存的訂單對(duì)象傳遞進(jìn)來。訂單完成保存后會(huì)轉(zhuǎn)移到thankCustomer风题。

thankCustomer狀態(tài)是一個(gè)簡單的視圖狀態(tài)判导,后臺(tái)使用了/WEB-INF/flows/pizza/thankCustomer.jsp文件進(jìn)行展示:

<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>

該頁面提供了一個(gè)完成流程的鏈接,它展示了用戶與流程交互的唯一辦法沛硅。

Spring Web Flow為視圖的用戶提供了一個(gè)flowExecutionUrl變量眼刃,它包含了流程的URL。結(jié)束鏈接將一個(gè)_eventId參數(shù)關(guān)聯(lián)到URL上稽鞭,以便返回到Web流程時(shí)觸發(fā)finished事件鸟整。這個(gè)事件將會(huì)使流程到達(dá)結(jié)束狀態(tài)。

流程將會(huì)在結(jié)束狀態(tài)完成朦蕴。由于在流程結(jié)束后沒有下一步做什么具體信息,流程將會(huì)重新從identifyCustomer狀態(tài)開始弟头,以準(zhǔn)備接受下一個(gè)訂單沉颂。

下面還要定義identifyCustomer纺非、buildOrder、takePayment這些子流程。

收集顧客信息

對(duì)于一個(gè)顧客戳晌,需要收集其電話、住址等信息,如下面的流程圖:


識(shí)別顧客流程
識(shí)別顧客流程

這個(gè)流程不再是線性的,而是有了分支钳垮。例如在查找顧客后,流程可能結(jié)束额港,也可能轉(zhuǎn)到注冊(cè)表單饺窿。同樣的,在checkDeliveryArea狀態(tài)移斩,顧客可能會(huì)被告警肚医,也可能是不被告警。

程序清單:

<?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" />

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

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

    <view-state id="registrationForm" model="order" popup="true">
        <on-entry>
            <evaluate
                expression="order.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" />
</flow>

下面將這個(gè)流程定義分解成一個(gè)個(gè)的狀態(tài)向瓷。

詢問電話號(hào)碼

welcome狀態(tài)是一個(gè)很簡單的視圖狀態(tài)肠套,它歡迎訪問Spizza網(wǎng)站的顧客并要求輸入電話。它有兩個(gè)轉(zhuǎn)移:如果從視圖觸發(fā)phoneEntered事件猖任,就會(huì)定向到lookupCustomer你稚,另外一個(gè)就是在全局轉(zhuǎn)移中定義用來響應(yīng)cancel事件的cancel轉(zhuǎn)移。

頁面代碼:

<%@ 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>

這個(gè)簡單的表單用來讓用戶輸入電話號(hào)碼朱躺,有兩個(gè)特殊的部分入宦,首先是隱藏的_flowExecutionKey輸入。當(dāng)進(jìn)入視圖狀態(tài)時(shí)室琢,流程暫停并等待用戶采取一些行為乾闰。當(dāng)用戶提交表單時(shí),流程執(zhí)行鍵會(huì)在_flowExecutionKey輸入域中返回盈滴,并在流程暫停的位置進(jìn)行恢復(fù)涯肩。

還需要注意提交按鈕的名稱_eventId_部分是Spring Web Flow的一個(gè)線索,它表明了接下來要觸發(fā)事件巢钓。當(dāng)點(diǎn)擊這個(gè)按鈕提交表單時(shí)病苗,就會(huì)觸發(fā)phoneEntered事件,進(jìn)而轉(zhuǎn)移到lookupCustomer症汹。

查找顧客

當(dāng)歡迎顧客的表單提交后硫朦,顧客的電話號(hào)碼將包含在請(qǐng)求參數(shù)中,并用于查詢顧客背镇。lookupCustomer狀態(tài)的<evaluate>元素是查找發(fā)生的位置咬展。它將電話號(hào)碼從請(qǐng)求參數(shù)中抽取出來,并傳遞到pizzaFlowActions Bean的lookupCustomer()方法中瞒斩。該方法要么返回Customer對(duì)象破婆,要么拋出CustomerNotFoundException異常。

在前一種情況下胸囱,Customer對(duì)象會(huì)被設(shè)置到customer變量中(通過result屬性)并默認(rèn)的轉(zhuǎn)移將流程帶到customerReady狀態(tài)祷舀。如果沒有查到顧客,那么會(huì)拋出異常,流程會(huì)轉(zhuǎn)移到registrationForm狀態(tài)裳扯。

注冊(cè)新顧客

registrationForm要求用戶填寫配送地址:

<%@ 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>

該表單綁定到了Order.customer對(duì)象上抛丽。

檢查配送區(qū)域

顧客提供了地址后,需要確認(rèn)住址是否在配送范圍內(nèi)饰豺,因此使用了決策狀態(tài)亿鲜。

決策狀態(tài)checkDeliveryArea有一個(gè)<if>元素,它將顧客的郵編傳遞到pizzaFlowActions Bean的checkDeliveryArea()方法中哟忍,該方法會(huì)返回一個(gè)Boolean值狡门。

如果顧客在配送范圍內(nèi),那么流程將轉(zhuǎn)移到addCustomer狀態(tài)锅很,否則進(jìn)入deliveryWarning視圖狀態(tài)其馏。deliveryWarnin視圖:

<%@ 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>

其中有兩個(gè)鏈接,允許用戶繼續(xù)訂單或者取消訂單爆安。通過使用與welcome狀態(tài)相同的flowExecutionUrl變量叛复,這些鏈接分別觸發(fā)流程中的accept和cancel事件。如果發(fā)送的是accept事件扔仓,那么流程會(huì)轉(zhuǎn)移到addCustomer狀態(tài)褐奥。否則,子流程會(huì)轉(zhuǎn)移到cancel狀態(tài)翘簇。

存儲(chǔ)顧客數(shù)據(jù)

addCustomer有一個(gè)<evaluate>元素撬码,它會(huì)調(diào)用pizzaFlowActions.addCustomer()方法,將order.customer流程參數(shù)傳遞進(jìn)去版保。

一旦這個(gè)流程完成呜笑,就會(huì)執(zhí)行默認(rèn)轉(zhuǎn)移,流程會(huì)轉(zhuǎn)移到ID為customerReady的結(jié)束狀態(tài)彻犁。

結(jié)束流程

當(dāng)customer流程完成所有的路徑后叫胁,會(huì)到達(dá)customerReady的結(jié)束狀態(tài)。當(dāng)調(diào)用它的披薩流程恢復(fù)時(shí)汞幢,它會(huì)接收到一個(gè)customerReady事件驼鹅,這個(gè)事件將使得流程轉(zhuǎn)移到buildOrder狀態(tài)。

注意森篷,customerReady結(jié)束狀態(tài)包含了一個(gè)<output>元素输钩。在流程中,它等同于Java的return語句疾宏。它會(huì)從子流程中傳遞一些數(shù)據(jù)到調(diào)用流程张足。例如,<output>元素返回customer變量坎藐,這樣披薩流程中的identifyCustomer子流程狀態(tài)就可以將其指定給訂單。

另外,如果用戶在任意地方觸發(fā)了cancel事件岩馍,將會(huì)通過cancel狀態(tài)結(jié)束流程碉咆,這也會(huì)在披薩流程中觸發(fā)cancel事件并導(dǎo)致轉(zhuǎn)移到披薩流程的結(jié)束狀態(tài)。

構(gòu)建訂單

下面就是確定顧客想要什么樣的披薩蛀恩,提示用戶創(chuàng)建披薩并將其放入訂單疫铜,如圖:


通過訂單子流程添加披薩
通過訂單子流程添加披薩

可以看到,showOrder狀態(tài)位于訂單子流程的中心位置双谆。這是用戶進(jìn)入這個(gè)流程時(shí)的狀態(tài)壳咕,也是用戶添加披薩訂單后轉(zhuǎn)移的目標(biāo)狀態(tài)。它展現(xiàn)了訂單的當(dāng)前狀態(tài)顽馋,并允許用戶添加其他的披薩到訂單中谓厘。

添加披薩訂單時(shí),會(huì)轉(zhuǎn)移到createPizza狀態(tài)寸谜。這是一個(gè)視圖狀態(tài)竟稳,允許用戶對(duì)披薩進(jìn)行選擇。

在showOrder狀態(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">

    <input name="order" required="true" />

    <!-- Order -->
    <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.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" />
</flow>

這個(gè)子流程實(shí)際上回操作主流程創(chuàng)建的Order對(duì)象果善,在這里我們使用<input>元素來將Order對(duì)象傳遞進(jìn)流程诊笤。

接下來會(huì)看到showOrder狀態(tài),它是一個(gè)基本的視圖狀態(tài)巾陕,具有3個(gè)不同的轉(zhuǎn)移讨跟,分別用于創(chuàng)建披薩、提交訂單和取消訂單惜论。

createPizza的視圖是一個(gè)表單许赃,這個(gè)表單可以添加新的Pizza對(duì)象到訂單。<on-entry>元素添加了一個(gè)新的Pizza對(duì)象到流程作用域內(nèi)馆类,當(dāng)表單提交時(shí)它將填充進(jìn)訂單混聊。值得注意的是,這個(gè)視圖狀態(tài)引用的model是流程作用域同一個(gè)Pizza對(duì)象乾巧。Pizza對(duì)象將綁定到創(chuàng)建披薩的表單中:

<%@ 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/>
      <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>

當(dāng)通過Continue按鈕提交訂單時(shí)句喜,尺寸和配料選擇會(huì)綁定到Pizza對(duì)象中,并且觸發(fā)addPizza轉(zhuǎn)移沟于。與這個(gè)轉(zhuǎn)移關(guān)聯(lián)的<evaluate>元素表明在轉(zhuǎn)移到showOrder狀態(tài)之前咳胃,流程作用域內(nèi)的Pizza對(duì)象會(huì)傳遞給訂單的addPizza()方法中。

有兩種方法可以結(jié)束流程旷太,用戶可以點(diǎn)擊showOrder視圖中的Cancel按鈕或者Checkout按鈕展懈。這兩種操作都會(huì)使流程轉(zhuǎn)移到一個(gè)<end-state>销睁。但是選擇的結(jié)束狀態(tài)ID決定了退出這個(gè)流程時(shí)觸發(fā)事件,進(jìn)而最終確定主流程的下一個(gè)行為存崖。主流程要么基于cancel要么基于orderCreated事件進(jìn)行狀態(tài)轉(zhuǎn)移冻记。在前者情況下,外邊的流程會(huì)結(jié)束来惧;后者冗栗,會(huì)轉(zhuǎn)移到takePayment子流程。

支付

在披薩流程要結(jié)束的時(shí)候供搀,最后的子流程提示用戶輸入他們的支付信息隅居,如下圖:

支付子流程
支付子流程

支付子流程也是使用<input>元素接收一個(gè)Order對(duì)象作為輸入。

可以看到葛虐,進(jìn)入支付子流程的時(shí)候胎源,用戶會(huì)到達(dá)takePayment狀態(tài)。這是一個(gè)視圖狀態(tài)挡闰,在這里用戶可以選擇信用卡乒融、支票或者現(xiàn)金進(jìn)行支付。提示支付信息后摄悯,進(jìn)入verifyPayment狀態(tài)赞季,這是一個(gè)行為狀態(tài),會(huì)校驗(yà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">

    <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" />
</flow>

在流程進(jìn)入takePayment視圖時(shí)申钩,<on-entry>元素將構(gòu)建一個(gè)支付表單并使用SpEL表達(dá)式在流程范圍內(nèi)創(chuàng)建PaymentDetails實(shí)例,該實(shí)例實(shí)際上是表單背后的對(duì)象瘪阁。它也會(huì)創(chuàng)建視圖作用域的paymentDetails變量撒遣,這個(gè)變量是一個(gè)包含了PaymentType enum的值的列表。在這里管跺,SpEL的T()作用于PaymentType類义黎,這樣就可以調(diào)用靜態(tài)的asList()方法。

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('_', ' '));
    }
}

在面對(duì)支付表單的時(shí)候豁跑,用戶可能提交支付廉涕,也可能會(huì)取消。根據(jù)做出的選擇艇拍,支付子流程將名為paymentTaken或cancel的<end-state>結(jié)束狐蜕。就像其他的子流程一樣,不論哪種<end-state>都會(huì)結(jié)束子流程并將控制交給主流程卸夕。但是所采用的id將決定主流程接下來的轉(zhuǎn)移层释。

目前我們已經(jīng)依次介紹了披薩流程及其子流程,下面快速了解下如何對(duì)流程及其狀態(tài)的訪問增加安全保護(hù)快集。

保護(hù)Web流程

Spring Web Flow中的狀態(tài)贡羔、轉(zhuǎn)移甚至整個(gè)流程都可以借助<secured>元素實(shí)現(xiàn)安全性廉白,該元素會(huì)作為這些元素的子元素。例如治力,為了保護(hù)對(duì)一個(gè)視圖狀態(tài)的訪問:

<view-state id="restricted">
    <secured attributes="ROLE_ADMIN" match="all"/>
</view-state>

按照這里的配置蒙秒,只有授權(quán)ROLE_ADMIN訪問權(quán)限(借助attributes屬性)的用戶才能訪問這個(gè)視圖狀態(tài)勃黍。attributes屬性使用逗號(hào)分隔的權(quán)限列表來表明用戶要訪問指定狀態(tài)宵统、轉(zhuǎn)移或流程所需要的權(quán)限。match屬性可以設(shè)置為any或all覆获。如果是any马澈,那么用戶至上具備一個(gè)attributes屬性所列的權(quán)限。如果的all弄息,那么用戶必須具有所有權(quán)限痊班。具體見下一章。



如果覺得有用摹量,歡迎關(guān)注我的微信涤伐,有問題可以直接交流:

你的關(guān)注是對(duì)我最大的鼓勵(lì)!
你的關(guān)注是對(duì)我最大的鼓勵(lì)缨称!
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末凝果,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子睦尽,更是在濱河造成了極大的恐慌器净,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,807評(píng)論 6 518
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件当凡,死亡現(xiàn)場離奇詭異山害,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)沿量,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,284評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門浪慌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人朴则,你說我怎么就攤上這事权纤。” “怎么了佛掖?”我有些...
    開封第一講書人閱讀 169,589評(píng)論 0 363
  • 文/不壞的土叔 我叫張陵妖碉,是天一觀的道長。 經(jīng)常有香客問我芥被,道長欧宜,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,188評(píng)論 1 300
  • 正文 為了忘掉前任拴魄,我火速辦了婚禮冗茸,結(jié)果婚禮上席镀,老公的妹妹穿的比我還像新娘。我一直安慰自己夏漱,他們只是感情好豪诲,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,185評(píng)論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著挂绰,像睡著了一般屎篱。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上葵蒂,一...
    開封第一講書人閱讀 52,785評(píng)論 1 314
  • 那天交播,我揣著相機(jī)與錄音,去河邊找鬼践付。 笑死秦士,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的永高。 我是一名探鬼主播隧土,決...
    沈念sama閱讀 41,220評(píng)論 3 423
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼命爬!你這毒婦竟也來了曹傀?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 40,167評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤遇骑,失蹤者是張志新(化名)和其女友劉穎卖毁,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體落萎,經(jīng)...
    沈念sama閱讀 46,698評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡亥啦,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,767評(píng)論 3 343
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了练链。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片翔脱。...
    茶點(diǎn)故事閱讀 40,912評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖媒鼓,靈堂內(nèi)的尸體忽然破棺而出届吁,到底是詐尸還是另有隱情,我是刑警寧澤绿鸣,帶...
    沈念sama閱讀 36,572評(píng)論 5 351
  • 正文 年R本政府宣布疚沐,位于F島的核電站,受9級(jí)特大地震影響潮模,放射性物質(zhì)發(fā)生泄漏亮蛔。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,254評(píng)論 3 336
  • 文/蒙蒙 一擎厢、第九天 我趴在偏房一處隱蔽的房頂上張望究流。 院中可真熱鬧辣吃,春花似錦、人聲如沸芬探。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,746評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽偷仿。三九已至哩簿,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間炎疆,已是汗流浹背卡骂。 一陣腳步聲響...
    開封第一講書人閱讀 33,859評(píng)論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留形入,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,359評(píng)論 3 379
  • 正文 我出身青樓缝左,卻偏偏與公主長得像亿遂,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子渺杉,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,922評(píng)論 2 361

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

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理蛇数,服務(wù)發(fā)現(xiàn),斷路器是越,智...
    卡卡羅2017閱讀 134,716評(píng)論 18 139
  • Spring Web Flow是Spring MVC的擴(kuò)展倚评,它支持開發(fā)基于流程的應(yīng)用程序浦徊。比如在網(wǎng)上商城購買時(shí),需...
    yjaal閱讀 1,808評(píng)論 0 2
  • 三、組合起來:披薩流程 這里我們通過訂購披薩的過程對(duì)流程進(jìn)行說明呢岗。我們首先從構(gòu)建一個(gè)高層次的流程開始冕香,它定義了訂購...
    yjaal閱讀 1,239評(píng)論 0 1
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,867評(píng)論 6 342
  • 如今做Java尤其是web幾乎是避免不了和Spring打交道了,但是Spring是這樣的大而全后豫,新鮮名詞不斷產(chǎn)生悉尾,...
    MageekChiu閱讀 1,627評(píng)論 0 26