你是否覺(jué)得當你的Web應用越來(lái)越復雜,理解和管理頁(yè)面流程—驅動(dòng)你應用程序用例的樂(lè )譜—也越來(lái)越困難了呢?而被迫使用特定的方式做事情并且無(wú)法重用是不是讓你感覺(jué)很累?你是否覺(jué)得使用了太多時(shí)間開(kāi)發(fā)你自己特定的方法去解決普遍問(wèn)題就像會(huì )話(huà)狀態(tài)管理?
進(jìn)入Spring Web Flow。
Spring Web Flow (SWF) 是Spring Framework的一個(gè)脫離模塊。這個(gè)模塊是Spring Web應用開(kāi)發(fā)模塊棧的一部分,Spring Web包含Spring MVC。
Spring Web Flow 的目標是成為管理Web應用頁(yè)面流程的最佳方案。當你的應用需要復雜的導航控制,例如向導,在一個(gè)比較大的事務(wù)過(guò)程中去指導用戶(hù)經(jīng)過(guò)一連串的步驟的時(shí)候,SWF將會(huì )是一個(gè)功能強大的控制器。
以下是一個(gè)受控制的導航的例子,使用UML狀態(tài)圖描述:

圖 1 - 一個(gè)航空訂票服務(wù)流程
聰明的讀者會(huì )認出這個(gè)是一個(gè)典型的航空訂票流程,就是那種每次你通過(guò)在線(xiàn)方式參與訂票的過(guò)程。
在傳統的Web應用中,頁(yè)面流程就像上面所展示的,不是很明確 — 他們不是一等公民。就拿一個(gè)使用Struts的Web應用舉個(gè)例子,為了在Struts里面實(shí)現頁(yè)面流,大多數開(kāi)發(fā)人員利用了框架提供的Action和視圖。在這種情況下,一個(gè)單獨的Action就和一個(gè)指定的請求URL產(chǎn)生了聯(lián)系。只有當請求從那個(gè)URL過(guò)來(lái)的時(shí)候,Action才會(huì )被執行。在執行過(guò)程中,Action運行一些處理并且選擇一個(gè)合適的視圖顯示結果。這非常簡(jiǎn)單。
所以要在Struts中實(shí)現多步控制的頁(yè)面流,你需要通過(guò)不同的視圖將獨立的Action形成鏈。用來(lái)處理不同事件,例如“后退”或“提交”的 Action URL都是硬編碼在視圖中的。一些ad-hoc形式的會(huì )話(huà)存儲被用來(lái)管理流程狀態(tài)。提交后重定向被用來(lái)阻止重復提交,等等。
雖然這是一個(gè)簡(jiǎn)單并且有效的方法,但是它具有一個(gè)很大的缺陷:從struts-config.xml文件的Action定義中不能清晰的看到頁(yè)面流程。你無(wú)法從幾棵樹(shù)看到一片森林,就像你無(wú)法從Action和視圖的定義看到頁(yè)面流程一樣。靈活性也因為Action和視圖不能被重用而大打折扣。最后,你仍然需要做很多工作,這并不見(jiàn)得容易。
Spring MVC提供了一個(gè)輕便的高層次的功能:表單控制器實(shí)現了一個(gè)與定義的頁(yè)面流程。它提供了兩個(gè)這樣的控制器:SimpleFormController和AbstractWizardController。盡管如此,這些仍然是大多數頁(yè)面流程控制概念的例子。
Tapestry和JSP在頁(yè)面的基礎上而不是請求的基礎上使用事件驅動(dòng)的方法,使得每個(gè)頁(yè)面和它的后退控制器邏輯保持一致。然而,仍然需要提供一個(gè)優(yōu)秀的類(lèi)根據一個(gè)定義良好的能跨越多個(gè)頁(yè)面和不同路徑的生存周期去支持一個(gè)邏輯頁(yè)面流程。就如你所看到的,這個(gè)頁(yè)面流程的生存周期要比單一的請求長(cháng),但是卻比一個(gè)會(huì )話(huà)要短。
這就是Spring Web Flow的切入點(diǎn),允許你使用一個(gè)簡(jiǎn)單清晰的方法體現你的頁(yè)面流程,并且隨時(shí)重用,包括像Struts、Spring MVC、Tapestry、JSP甚至Portlets這些環(huán)境下。
正如你所看到的,Spring Web Flow提供以下優(yōu)點(diǎn):
現在已經(jīng)有能力說(shuō)Web流程是一組狀態(tài)(states)的集合。一個(gè)狀態(tài)是流程中發(fā)生某事的一個(gè)點(diǎn)。舉個(gè)例子,譬如顯示一個(gè)視圖或者執行一個(gè)Action。每個(gè)狀態(tài)都有一個(gè)或更多的轉變(transitions)用來(lái)移動(dòng)到下一個(gè)狀態(tài)。
一個(gè)轉變是由一個(gè)事件(event)觸發(fā)的。
為了展示一個(gè)Web流程的定義,下面的XML片段展示了上面UML狀態(tài)圖定義的航空訂票處理:
圖 2 - 基于XML的航空訂票流程定義
就像你所看到的,僅僅是掃過(guò)XML定義,邏輯流程驅動(dòng)的訂票流程處理就已經(jīng)可以清晰地辨認出來(lái)了,即使你都不了解Spring Web Flow實(shí)現細節。
如果你看得仔細點(diǎn),你將會(huì )發(fā)現兩個(gè)子流程產(chǎn)生了訂票流程的子過(guò)程。第一個(gè)子流程指導用戶(hù)輸入乘客信息。第二個(gè)讓用戶(hù)分配他的座位。這個(gè)內嵌的流程扮演了“迷你應用程序模塊”的角色,這是Spring Web Flow強大的功能之一。
你可以將這份定義上交給一位業(yè)務(wù)分析人員,并且她估計能看懂。更好的是你可以根據這個(gè)定義繪制一個(gè)可視化圖表將其提交給業(yè)務(wù)分析人員。做這個(gè)的工具已經(jīng)誕生了。
這篇文章的下一部分將逐塊分解上面的航空訂票流程定義,并且提供對話(huà)框演示Spring Web Flow是如何工作的。
流程定義
這是第一行的XML流程定義:
...
webflow 元素定義了流程,指定它的id和start-date。id是一個(gè)簡(jiǎn)單的唯一的標識符,start-state是一個(gè)轉變的初始狀態(tài),這發(fā)生在當一個(gè)新的流程會(huì )話(huà)在運行時(shí)被激活的時(shí)候。
所以,在業(yè)務(wù)案例上,當訂票會(huì )話(huà)被激活的時(shí)候,它將轉變?yōu)閛btainTripInfo狀態(tài)。
獲得行程信息行為狀態(tài)(Action State)
下面是obtainTripInfo狀態(tài)定義。
記得當狀態(tài)被進(jìn)入,針對該狀態(tài)的行為就發(fā)生了。正如你將看到的,不同的狀態(tài)類(lèi)型有不同的執行動(dòng)作。action state,正如obtainTripInfo,在進(jìn)入的時(shí)候執行一個(gè)Action。該Action返回執行的邏輯結果,并且這個(gè)結果被映射到狀態(tài)轉變上。一切就是這么簡(jiǎn)單。
所以,在這個(gè)業(yè)務(wù)案例上,obtainTripInfo,當進(jìn)入的時(shí)候執行bookingActions這個(gè)Action的 bindAndValidate方法。這個(gè)方法從瀏覽器綁定表單輸入到一個(gè)Trip領(lǐng)域對象并且檢驗它。如果處理成功,就進(jìn)入 suggestItineraries狀態(tài)。如果錯誤發(fā)生,進(jìn)入tryAgain狀態(tài)。
訂票Action
當在Spring IoC中使用Spring Web Flow的時(shí)候,action元素的bean屬性涉及到Spring Application Context中定義的一個(gè)相同名稱(chēng)的Action實(shí)現。下面是bookingActions的定義:
web-context.xml
這就允許我們的Action實(shí)現被Spring管理并且通過(guò)依賴(lài)注入進(jìn)行配置。
建議路線(xiàn)行為狀態(tài)
現在我們看一下下一個(gè)Action State,給定一個(gè)綁定的并且通過(guò)檢驗的Trip對象作為輸入,返回一個(gè)建議的路線(xiàn)集合:
下面是Action實(shí)現代碼:
public class BookingActions extends FormAction { ... public Event suggestItineraries(RequestContext context) { Trip trip = (Trip)context.getRequestScope().getAttribute("trip"); Collection itineraries = bookingAgent.suggestItineraries(trip); context.getRequestScope().setAttribute("itineraries", itineraries); return success(); }} 當進(jìn)入suggestItineraries狀態(tài)的時(shí)候,suggestItineraries就被調用了。其他的Action State也是同樣的工作方式:進(jìn)入狀態(tài)并調用指定的方法。
顯示建議路線(xiàn)視圖狀態(tài)(View State)
一旦返回了一個(gè)建議的路線(xiàn)集合,下一步就是讓用戶(hù)看到它們并且讓用戶(hù)選擇他最喜歡的。這可以通過(guò)以下的狀態(tài)定義完成:
就如你所看到的,displaySuggestedItineraries是一個(gè)view state - 一個(gè)我們還未討論過(guò)的狀態(tài)類(lèi)型。一個(gè)視圖狀態(tài),當進(jìn)入的時(shí)候,導致執行流程暫停,并將控制返回給客戶(hù)端同時(shí)根據配置的視圖同時(shí)返回。隨后,在用戶(hù)思考過(guò)后,客戶(hù)端發(fā)出一個(gè)事件描述用戶(hù)執行的Action。繼續流程,事件的發(fā)生已經(jīng)映射到了一個(gè)狀態(tài)的轉變,這個(gè)轉變把用戶(hù)帶到了流程的下一步。
所以,在這個(gè)業(yè)務(wù)案例上,當進(jìn)入displaySuggestedItineraries的時(shí)候suggestedIteneraries視圖被渲染并且將控制返回給瀏覽器。然后用戶(hù)選擇路線(xiàn)之后點(diǎn)擊“選擇”按鈕。這就出發(fā)了select事件,傳遞選擇的路線(xiàn)id作為事件參數。
用戶(hù)也可能選擇startOver,這時(shí)候流程轉變到了取消狀態(tài)。
對于view屬性,Spring MVC中,FlowControoler使用熟悉的 ModelAndView和ViewResolver構造,在Struts中,FlowAction用ActionForward。
客戶(hù)端狀態(tài)
在這個(gè)問(wèn)題上你可能會(huì )問(wèn):
“...自從進(jìn)入ViewState之后,執行流程暫停了,控制返回給了瀏覽器,那么流程如何重新拾起并且繼續運行呢?”
答案就是客戶(hù)端跟蹤一個(gè)唯一的id用戶(hù)標示流程執行點(diǎn),并且將這個(gè)id放在input標簽內,以便引起下一個(gè)事件。典型的做法是放在一個(gè)隱藏域內。
舉個(gè)例子,在一個(gè)JSP文件里:
">
“是否需要乘客信息?” 決策狀態(tài)(Decision State)
用戶(hù)選擇了她需要的路線(xiàn)之后,流程需要做一個(gè)上下文關(guān)系(contextual)的決策關(guān)于下一步執行什么。
需要特別指出的是,如果用戶(hù)沒(méi)有登錄,或者她已經(jīng)登錄但是希望確認她的信息 - 例如她所使用的信用卡 - 流程控制需要允許她確定這些信息。另一方面,如果她已經(jīng)登錄并且希望直接進(jìn)入預定頁(yè)面,流程控制應該跳個(gè)這個(gè)可選步驟。
基本上需要做一個(gè)動(dòng)態(tài)的決策重新考慮她的信息和偏好的。
決策狀態(tài)最適合這個(gè),看下面的定義:
輸入乘客信息子流程狀態(tài)(SubFlow State)
處理乘客信息的過(guò)程邏輯上獨立的。這是處理的一部分,但是在機票預定這個(gè)上下文環(huán)境之外它也可以獨立存在。
子流程(Subflow)狀態(tài)機制就是針對這個(gè)實(shí)現的。當進(jìn)入一個(gè)子流程狀態(tài),這個(gè)子流程就被產(chǎn)生了。父流程掛起知道子流程結束。這讓你可以把你的應用作為一系列自包含的模塊看待,至于流程,你可以很容易的把多種情況統一處理。
看一下enterPassengerInformation子流程狀態(tài):
flow 屬性是這個(gè)進(jìn)入這個(gè)流程的id,attribute-mapper 元素從子流程映射屬性。輸入映射將屬性向下映射到子流程。輸出映射將屬性倒退會(huì )父流程當子流程結束的時(shí)候。你可以從這里看到表達式也是支持的。
所以,在這個(gè)業(yè)務(wù)用例上,當進(jìn)入enterPassengerInformation狀態(tài),乘客流程就產(chǎn)生了。passengerId屬性傳遞給這個(gè)流程作為輸入。從這里,自流程作它需要做的。對于父流程來(lái)說(shuō)這是一個(gè)黑箱。當子流程結束,父流程繼續,應答最后結果并決定去哪執行下一步 — 在這里,去確認預定。
顯示確認結束狀態(tài)(End State)
最有一個(gè)狀態(tài)類(lèi)型在這里討論:結束狀態(tài)。當進(jìn)入結束狀態(tài),活動(dòng)的流程會(huì )話(huà)就結束了。在結束上面,所有與之相關(guān)的資源都被自動(dòng)清理。
displayConfirmation結束狀態(tài)在一條路線(xiàn)被被成功預定后顯示確認信息:
當進(jìn)入這個(gè)狀態(tài)的時(shí)候,訂票流程結束了并且顯示reservationConfirmation視圖。因為訂票流程是根流程,并非子流程,所以任何分配的資源都會(huì )被自動(dòng)清理。
注意:結束流程如果是一個(gè)子流程,進(jìn)入這個(gè)狀態(tài)就會(huì )被認為是一個(gè)子流程結果并繼續父流程。更特別的是,這個(gè)狀態(tài)的ID在繼續父流程的子流程的狀態(tài)上被用作一個(gè)狀態(tài)的轉變。你可以從enterPassengerInformation子流程狀態(tài)定義上看出來(lái)。注意它如何響應子流程的“完成”結果,是通過(guò)一個(gè)“完成”結束狀態(tài)。
到這里,你了解了Spring Web Flow是關(guān)于什么的,并且你也看到了一個(gè)現實(shí)的例子?,F在你要看到的就是如何部署這個(gè)流程定義到特定的環(huán)境中去執行,就行Spring MVC在一個(gè)Servlet環(huán)境下一樣:
做這事是很容易的,這里你需要和Spring MVC一起使用:
這就自動(dòng)將bookingFlow導出至/booking.htm這個(gè)URL在一個(gè)Servlet環(huán)境里。
下面的部分介紹了一些SWF更高級的特性。
FlowExecutionListener 構造了一個(gè)觀(guān)察者允許你監聽(tīng)并且對一個(gè)執行著(zhù)的流程的生存周期作出反應。你可以使用這個(gè)特性作任何事,從一個(gè)狀態(tài)的預處理到后期條件的檢測,或則審計、安全處理。
一個(gè)執行著(zhù)的流程的狀態(tài)的存儲機制是完全可插拔的?;贖ttpSession的存儲是默認的,但是SWF提供兩種其他的存儲方式:一個(gè)是使用服務(wù)器端連續的會(huì )話(huà)儲存,另一種是使用完全的客戶(hù)端序列化。定義你自己的存儲方式,舉個(gè)例子,譬如使用數據庫存儲,是不推薦的。
你應該注意到Spring Web Flow并不是一攬子全包的解決方案。正如你所看到的,這是一個(gè)有狀態(tài)的系統能夠自動(dòng)管理這些由業(yè)務(wù)處理過(guò)程驅動(dòng)的頁(yè)面流程。它不能被當作簡(jiǎn)單的、無(wú)狀態(tài)的解決方案。舉個(gè)例子,它不能被用在一些需要自由導航的站點(diǎn),一些可以讓用戶(hù)自由“點(diǎn)擊周?chē)我怄溄印钡恼军c(diǎn)。Spring Web Flow被設計為強大的受控制導航,可以指導用戶(hù)按照一個(gè)清晰的業(yè)務(wù)目的和生存周期進(jìn)行處理。
為了使得用例更具體,這里有一些“不錯的流程”的例子,這些流程就適合使用SWF系統:
下面的一些例子是不適合使用SWF的:
Spring Web Flow打算要作為一個(gè)優(yōu)秀的傳統的控制器在任何Web環(huán)境下,就像Spring MVC、Struts、Tapestry、Web Work、JSP或者Portlets一樣。一個(gè)單一的站點(diǎn)可以適當的組合使用簡(jiǎn)單的控制器管理Web流程。
Spring Web Flow 1.0 final 版本將隨著(zhù)Spring 1.3正式版發(fā)布,時(shí)間定在JavaOne大會(huì )前大概六月份的時(shí)候。就現在而言,只能期待正式、穩定的預覽版。這個(gè)產(chǎn)品目前已經(jīng)在特性集合和示例程序方面相當成熟了。
當開(kāi)發(fā)小組給最終發(fā)布版砌上最后一塊磚時(shí),下面是一些最重要的特性我們長(cháng)在著(zhù)手完成:
作為獨立的庫,Spring Web Flow很好與其他框架整合了。除了Spring MVC以外,已經(jīng)提供了和Struts、Portlet MVC的整合,JSP和Tapestry的整合在最終版中也會(huì )見(jiàn)到。
在Spring 1.2中,在MBeanServer中輸出用于管理和監視的bean是很容易的。一個(gè)FlowExecutionMBean管理接口已經(jīng)存在了,我們計劃擴展以便可以從JMX控制臺集中監控在服務(wù)器執行的所有流程的全局統計數據。
系統中的每個(gè)結構都可以做成可插拔的以獲得簡(jiǎn)單的擴展或定制,甚至是從xml定義中。這包括狀態(tài)和轉變,以及其中的其他概念。
提供的特性和例子程序展示了在執行過(guò)程中使用事務(wù)補償來(lái)回滾先前提交的事務(wù),我們對這點(diǎn)很感興趣。
Spring Web Flow 是控制業(yè)務(wù)處理流程的有效解決方案。并且用起來(lái)也很有意思,如果你還沒(méi)試過(guò),那么你還等什么呢?
Spring Web Flow is covered in the Core Spring training course offered by Interface21 - http://www.springframework.com/training
The Spring Framework, http://www.springframework.org/
The Spring Web Flow Wiki, http://opensource.atlassian.com/confluence/spring/display/WEBFLOW/Home
The kdonald blog, http://www.jroller.com/page/kdonald
Struts, http://struts.apache.org/
Java Server Faces, http://java.sun.com/j2ee/javaserverfaces/
Tapestry, http://jakarta.apache.org/tapestry
WebWork, http://www.opensymphony.com/webwork/
JavaOne, http://java.sun.com/javaone/
聯(lián)系客服