第四版的第八章內(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ì)算的:

另外,你也可以不使用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è)披薩添加到訂單、提供支付信息尺铣,然后提交訂單拴曲,等待披薩上來,如下圖:

下面展示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è)顧客戳晌,需要收集其電話、住址等信息,如下面的流程圖:

這個(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)注我的微信涤伐,有問題可以直接交流:
