本教程經過一個簡化的購物車應用,介紹瞭如何使用 Spring Web Flow 2.0 來構建 Web 應用程序。本教程以講解實例爲主,爲了讀者更好地理解 Spring Web Flow ,也有部分理論的解釋。 html
本教程要求讀者具有 Java Web 應用的基本知識、熟悉 Spring Framework 的應用。 java
運行本教程中的示例,須要下列工具: web
回頁首 spring
Spring Web Flow 是 Spring 的一個子項目,其最主要的目的是解決跨越多個請求的、用戶與服務器之間的、有狀態交互問題。最新版本爲 2.0 ,相比於 1.x 版的 Spring Web Flow ,有如下幾個值得注意的新特性。 express
與 Spring MVC 深度整合 服務器
Spring Web Flow 1.x 是個自成體系的框架,可與 Spring Web MVC 、 Struts 、 JSF 等 Web 框架整合。最新的 Spring Web Flow 2.0 則明確聲明是基於 Spring Web MVC 的一個擴展。 session
提供了處理 Ajax 事件的能力 mvc
Ajax 事件的處理與 Web Flow 事件的處理相一致,在處理完成後, flow 便可刷新客戶端相關界面代碼。 app
與 JSF 整合 框架
經過將 JSF 層層包裝,最終可在 Spring Framework 和 Spring Web Flow 中使用 JSF 的各類組件。
與 Spring Security (原 Acegi Security )整合
只需將某個 flow 聲明爲「 secured 」,即 可利用 Spring Security 來肯定當前用戶是否有權限運行 flow 、激發事件等等。
更簡潔的配置
官方的數聽說同一個 flow , 2.0 版的配置比 1.x 版的配置少 50% 的 XML 代碼。
重用更方便
Spring Web Flow 2.0 提供了 flow 的繼承,重用即有的 flow 代碼更加容易。
重用更方便
Spring Web Flow 2.0 提供了 flow 的繼承,重用即有的 flow 代碼更加容易。
本教程主要討論 Web Flow 模塊的使用,對其餘的特性沒有涉及。
要了解 Spring Web Flow 是什麼東西,最好的辦法莫過於查看示例,圖 2 展現了一個簡化的購物車的流程。
圖 2 所示流程用 Spring Web Flow 2.0 的配置文件表示以下:
…… <flow> <view-state id="viewCart"> <transition on="submit" to="viewOrder"/> </view-state> <view-state id="viewOrder"> <transition on="confirm" to="viewConfirmed"/> </view-state> <view-state id="viewConfirmed"> <transition on="returnToIndex" to="returnToIndex"/> </view-state> <end-state id="returnToIndex"/> </flow>
清單 1 省略了許多技術細節,展現的只是一個業務的流程,主要是爲了讓你們對 Spring Web Flow 的語義有個初始的印象。從清單 1 中,應注意到一個很重要的特徵—— Spring Web Flow 語義與 Servlet API 無關。更確切地講, Spring Web Flow 語義關注的是業務的流程,並未與 Sun 公司的 Web 規範緊密結合,這種描述是更高層次的抽象,差很少是在建模的角度來描述業務流程。
不過, Spring Web Flow 也並不是只有抽象,如今尚未哪種工具或語言能夠將一個模型直接轉換成相應的應用程序。 Spring Web Flow 更像是抽象建模和技術細節的混血兒,相比於湮沒在繁多的控制器和視圖中的 Web MVC 應用來說, Spring Web Flow 提供瞭如清單 1 所描述的更高層次的抽象,但同時它也整合了像 Unified EL 這樣的工具來控制技術上的細節。
Flow 可看做是客戶端與服務器的一次對話( conversation )。 Flow 的完成要由分多個步驟來實現,在 Spring Web Flow 的語義中,步驟指的就是 state 。 Spring Web Flow 提供了五種 state ,分別是 Action State 、 View State 、 Subflow State 、 Decision State 、 End State ,這些 state 可用於定義 flow 執行過程當中的各個步驟。除了 End State 外,其餘 state 均可以轉換到別的 state ,通常經過在 state 中定義 transition 來實現到其餘 state 的轉換,轉換的發生通常由事件( event )來觸發。
前面講了, Spring Web Flow 提供了描述業務流程的抽象能力,但對一種 Web 開發技術而言,僅有這些是不夠的。同時, Spring Web Flow 是否是可以取代其餘 Web MVC 技術?或者在任何狀況下都應優先使用 Spring Web Flow ?要回答這些問題,先來看一下 Spring Web Flow 所着力解決的技術問題。
Java Servlet 規範爲 Web 應用程序中用到的各類對象規定了三種範圍( scope ),分別是 request 範圍、 session 範圍和 application 範圍。
現實開發中最使人頭痛的莫過於 session 範圍, Java Servlet 規範指明可在 web.xml 中按以下方式配置 session 的有效時間爲100分鐘:
<session-config> <session-timeout>100</session-timeout> </session-config>
然而,現實中的 session 範圍更像是「雞肋」,把大量數據放入 session 會致使嚴重的效率問題,在分佈式的環境中處理 session 範圍更是一不當心就會出錯,但拋棄 session 又會給開發帶來許多不便。 request 範圍雖然說能存放量大的數據,但有效範圍有限。擺在開發者面前的不少用例都要求一種比 request 範圍要長,但又比 session 範圍要短的這麼一種有效範圍。
針對 Java Servlet 規範中的這個缺陷, Spring Web Flow 2.0 中提供瞭如下兩種範圍:
被其餘 flow 所調用的 flow 便可稱爲 subflow。
因爲 flow 是由開發人員本身定義的,可根據業務的需求自由改變, flow 範圍和 conversation 範圍的使用也就突破了 Java Servlet 規範中 session 範圍和 request 範圍的侷限,真正作到了自由定製。
能夠看出, Spring Web Flow 所着力解決的問題便是客戶端與服務器的對話( conversation )問題,這個範圍比 request 要長,而比 session 要短。爲實現 conversation 範圍(即 flow 範圍),須要付出效率上的代價,所以,並不是全部 Web 應用都適合使用 Spring Web Flow 。 Seth Ladd 等人所著 Expert Spring MVC and Web Flow 一書,對什麼時候使用Spring Web Flow,列出了以下表格。
解決方案 | 什麼時候使用 |
---|---|
Spring MVC Controller | 某個單獨的、只需較少業務邏輯就可建立的頁面,同時該頁面不是 flow 的一部分 |
Spring MVC SimpleFormController | 某個只涉及表單提交的頁面,如一個搜索框 |
Spring MVC AbstractWizardFormController | 由一系列導航頁面組成的業務過程 |
Spring Web Flow | 任何比較複雜的、有狀態的、須要在多個頁面之間跳轉的業務過程 |
Web Flow 做爲一個單獨的概念被提出來,也可算是 Spring Web Flow 的一大亮點。目前大多數 Web MVC 框架都把重點把在各類 controller 和形形色色的 view 技術上面,對 Web 應用流程自己的關注是不夠的, Web Flow 的提出就提供了一層抽象,設計者就能夠從 Web Flow 抽象層面來進行設計、開發。固然, Web Flow 不能理解爲只是 Web 頁面間的跳轉流程,定義 Spring Web Flow 的語義並不是只限於頁面之間的跳轉,而能夠是 Web 應用中的各類行爲。由此,用例的模型建構好之後,就可直接從該模型轉換到相應的 Web Flow,開發人員的設計變得更加直觀、有效。
另外,在 Spring Web Flow 中重用 Web Flow 是比較容易的。在定義 flow 、 state 時可經過繼承某個已有的 flow 或 state ,來避免重複定義。同時,一個 flow 能夠調用其它 flow ,就跟通常程序語言中在某個函數內部調用其它函數同樣方便。
Spring Web Flow 2.0 就是 Spring Web MVC 的一個擴展,若是粗略一些來說,所謂 flow 就至關於 Spring Web MVC 中一種特殊的 controller ,這種 controller 可經過 XML 文件加以配置,所以在使用 Spring Web Flow 2.0 前須先對 Spring Web MVC進行配置,步驟以下:
本示例應用將採用 eclipse Dynamic Web Project 嚮導默認生成的目錄結構,在 WEB-INF 目錄下添加 config 和 flows 子目錄,其中 config 子目錄用來存放各類配置文件, flows 子目錄下存放 Spring Web Flow 的定義文件。最後目錄如圖3所示:
只需將如下幾個 jar 包導入 /WEB-INF/lib 目錄下就能夠了:
爲使用 Spring Web MVC ,須在 web.xml 中聲明 DispatcherServlet ,見清單3:
<servlet> <servlet-name>CartServlet</servlet-name> <servlet-class> org.springframework.web.servlet.DispatcherServlet </servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value> /WEB-INF/config/web-application-config.xml </param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet>
要讓 DispatcherServlet 處理全部以 /spring/ 開頭的請求,見清單 4:
<servlet-mapping> <servlet-name>CartServlet</servlet-name> <url-pattern>/spring/*</url-pattern> </servlet-mapping>
開發基於 Spring Web Flow 的應用每每會有大量的配置,這些配置全放在一個文件中是不合適的。本示例參考 Spring Web Flow 2.0 自帶示例,將不一樣功能的配置文件分開。其中 web-application-config.xml 用於配置與 Web 應用全局相關的內容, Spring Web MVC 的相關配置放在 webmvc-config.xml 中,教程後面要添加的 Spring Web Flow 的配置則放在 webflow-config.xml 中。在 web-application-config.xml 中用 import 元素導入其餘的配置文件。 web-application-config.xml的內容見清單5:
<?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:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd"> <!-- 搜索 samples.webflow 包裏的 @Component 註解,並將其部署到容器中 --> <context:component-scan base-package="samples.webflow" /> <!-- 啓用基於註解的配置 --> <context:annotation-config /> <import resource="webmvc-config.xml"/> </beans>
加入註解功能是出於最後運行 Web Flow 示例的須要,在這裏只要知道註解功能已被啓用就能夠了。
webmvc-config.xml 主要用於配置 Spring Web MVC 。所要作的就是添加一個 view resolver (視圖解析器),用於將視圖名解析成真實的視圖資源。另外,再配置好 URL 請求的 handler (處理器),用於將 URL 請求定向到某個控制器,在本例中,用到的是 UrlFilenameViewController。
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/> <property name="prefix" value="/WEB-INF/jsp/"/> <property name="suffix" value=".jsp"/> </bean> <bean id="viewMappings" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"> <property name="defaultHandler"> <!-- UrlFilenameViewController 會將 "/index" 這樣的請求映射成名爲 "index" 的視圖 --> <bean class="org.springframework.web.servlet.mvc.UrlFilenameViewController" /> </property> </bean> </beans>
如今的 index.jsp 只是顯示一行文字。
<?xml version="1.0" encoding="utf-8" ?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>Cart Application</title> </head> <body> <h1>Hello!</h1> </body> </html>
將應用程序發佈到 Tomcat 容器,再經過 http://localhost:8080/CartApp/spring/index.jsp 訪問 index.jsp 頁面(應用程序所在文件夾名是 CartApp ),測試 Spring Web MVC 配置是否正確。若是一切正常,可獲得以下頁面:
配置好 Spring Web MVC 的環境後,接下來就能夠往裏面加入 Spring Web Flow 2.0 的配置。不過,要搞明白 Spring Web Flow 2.0 的配置,必須先要了解相關的理論知識。
FlowRegistry 是存放 flow 的倉庫,每一個定義 flow 的 XML 文檔被解析後,都會被分配一個惟一的 id ,並以 FlowDefinition 對象的形式存放在 FlowResigtry 中。 FlowRegistry 配置方式可參看清單 8。
如下的示例清單中的 XML 配置元素默認使用了 webflow 名字空間,這也是 Spring Web Flow 習慣上的名字空間,參看教程後面 webflow-config.xml 文件,能夠更多瞭解 webflow 名字空間。
<webflow:flow-registry id="flowRegistry">
<webflow:flow-location path="/WEB-INF/flows/shopping.xml" id=」shopping」/>
</webflow:flow-registry>
每一個 flow 都必需要有 id 來標識,若是在配置中省略,那麼該 flow 默認的 id 將是該定義文件的文件名去掉後綴所得的字符串。
FlowExecutor 是 Spring Web Flow 的一個核心接口,啓動某個 flow ,都要經過這個接口來進行。從配置角度來講,只要保證有個 FlowExecutor 就能夠了, Spring Web Flow 的默認行爲已經足夠。默認配置參看清單9。
<webflow:flow-executor id="flowExecutor" />
FlowRegistry 中註冊的 flow 可能會有多個,但前面介紹過,每一個 flow 都會有 id ,沒有配置的,也會有個默認值, FlowExecutor 就是經過 id 來找出要執行的 flow 。至於這個 id ,則是要由用戶來指定的。在默認配置狀況下,若是客戶端發送了以下URL請求:
http://localhost:8080/CartApp/spring/shopping
則從 Spring Web Flow 的角度來看,這個 URL 就表示客戶想要執行一個 id 爲「 shopping 」的 flow ,因而就會在 FlowRegistry 中查找名爲「 shopping 」的 flow,由FlowExecutor負責執行。
客戶端發送的請求,先會由 servlet 容器(本教程示例中即爲 Tomcat )接收, servlet 容器會找到相應的應用程序(本教程中即爲 CartApp ),再根據 web.xml 的配置找到出符合映射條件的 servlet 來處理。 Spring Web MVC 中處理請求的 servlet 是 DispatcherServlet ,若是請求的路徑知足 DispatcherServlet 的映射條件,則 DispatcherServlet 會找出 Spring IoC 容器中全部的 HandlerMapping ,根據這些 HandlerMapping 中匹配最好的 handler (通常狀況下都是 controller ,即控制器)來處理請求。當 Controller 處理完畢,通常都會返回一個 view (視圖)的名字,DispatcherServlet再根據這個view的名字找到相應的視圖資源返回給客戶端。
搞清楚 Spring Web MVC 處理請求的流程後,基本上就能夠明白要整合 Spring Web MVC 與 Spring Web Flow 所須要的配置了。爲了讓客戶端的請求變成執行某個 flow 的請求,要解決如下幾個問題:
如今,須要一種接收執行 flow 的請求,而後根據請求來啓動相應 flow的handler (處理器), Spring Web Flow 2.0 提供了兩種方案可供選擇。第一種方案是本身編寫實現了 FlowHandler 接口的類,讓這個類來實現這個功能。第二種方案是使用一個現成的叫作 FlowController 的控制器。第一種方案靈活性比較大,在許多場合可能也是惟一的選擇,但對每一個 flow 都須要編寫相應的 FlowHandler 。本教程的示例採用第二種方案,對 FlowHandler 的介紹可參看 Spring Web Flow 2.0 自帶的文檔。 FlowController 實際上是個適配器,通常來說,咱們只要明白 FlowController 可根據客戶端請求的結尾部分,找出相應的 flow 來執行。配置 FlowController只需指定FlowExecutor便可,具體配置見清單10:
<bean id="flowController" class="org.springframework.webflow.mvc.servlet.FlowController"> <property name="flowExecutor" ref="flowExecutor"/> </bean>
另外還需在 HandlerMapping 中指明 /shopping.do 請求由 flowController 來處理,配置見清單11:
<bean id="viewMappings" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"> <property name="mappings"> <value> /shopping.do=flowController </value> </property> …... </bean>
須要指出的是,無論設成 /shopping.do 仍是設成 /shopping ,或者 /shopping.htm ,效果都是同樣的, flowController 都會去找 id 爲 shopping的flow來執行。
清單 8 所示 FlowRegistry 的配置,其中省略了 flow-registry 元素中一項比較重要的屬性, flow-builder-services 。 flow-builder-services 屬性的配置指明瞭在這個 flow-registry 「倉庫」裏的 flow 的一些基本特性,例如,是用 Unified EL 仍是 OGNL 、 model (模型)對象中的數據在顯示以前是否須要先做轉換,等等。在本示例中,咱們須要在 flow-builder-services 屬性中指明 Spring Web Flow 中所用到的 view ,由 Spring Web MVC 的「 View Resolver 」來查找,由 Spring Web MVC 的「 View Class」來解析,最後呈現給客戶。具體配置參看清單12:
<webflow:flow-builder-services id="flowBuilderServices" view-factory-creator="mvcViewFactoryCreator"/> <bean id="mvcViewFactoryCreator" class="org.springframework.webflow.mvc.builder.MvcViewFactoryCreator"> <property name="viewResolvers" ref="viewResolver"/> </bean>
全部這些配置的目的無非是兩個:一是要讓客戶端的請求轉變成 flow 的執行,二是要讓 flow 執行過程當中、或執行結束後獲得的視圖能返還給客戶端。若是對這裏的講解還不是很清楚,可先看下一節實際的配置,再回過頭來看本章內容,以加深理解。
實現示例應用的購物車流程,可按如下步驟操做:
將如下幾個 jar 包導入 /WEB-INF/lib 目錄:
Spring Web MVC 相關的配置前面已經分析過了,完整的配置見清單 13 :
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"> </property> <property name="prefix" value="/WEB-INF/jsp/"> </property> <property name="suffix" value=".jsp"> </property> </bean> <bean id="viewMappings" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"> <!-- /shopping.do 請求由 flowController 來處理 --> <property name="mappings"> <value> /shopping.do=flowController </value> </property> <property name="defaultHandler"> <!-- UrlFilenameViewController 會將 "/index" 這樣的請求映射成名爲 "index" 的視圖 --> <bean class="org.springframework.web.servlet.mvc.UrlFilenameViewController" /> </property> </bean> <bean id="flowController" class="org.springframework.webflow.mvc.servlet.FlowController"> <property name="flowExecutor" ref="flowExecutor"/> </bean> </beans>
在 /WEB-INF/config 目錄下添加 webflow-config.xml 文件, schema 名字空間可直接複製清單 14 中的內容。
<?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:webflow="http://www.springframework.org/schema/webflow-config" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/webflow-config http://www.springframework.org/schema/webflow-config/spring-webflow-config-2.0.xsd"> <webflow:flow-executor id="flowExecutor"/> <!— 全部 flow 定義文件位置在此配置, flow-builder-services 用於配置 flow 的特性 --> <webflow:flow-registry id="flowRegistry" flow-builder-services="flowBuilderServices"> <webflow:flow-location path="/WEB-INF/flows/shopping.xml" id="shopping"/> </webflow:flow-registry> <!—Web Flow 中的視圖經過 MVC 框架的視圖技術來呈現 --> <webflow:flow-builder-services id="flowBuilderServices" view-factory-creator="mvcViewFactoryCreator"/> <!— 指明 MVC 框架的 view resolver ,用於經過 view 名查找資源 --> <bean id="mvcViewFactoryCreator" class="org.springframework.webflow.mvc.builder.MvcViewFactoryCreator"> <property name="viewResolvers" ref="viewResolver"/> </bean> </beans>
webflow-config.xml 建立完成之後,不要忘記在 web-application-config.xml 中添加 import 元素,將 webflow-config.xml 文件導入。
<import resource="webflow-config.xml"/>
在 /WEB-INF/flows 目錄下建立 shopping.xml 文件,描述了圖 2 所示的流程。
<?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"> <view-state id="viewCart" view="viewCart"> <transition on="submit" to="viewOrder"> </transition> </view-state> <view-state id="viewOrder" view="viewOrder"> <transition on="confirm" to="orderConfirmed"> </transition> </view-state> <view-state id="orderConfirmed" view="orderConfirmed"> <transition on="returnToIndex" to="returnToIndex"> </transition> </view-state> <end-state id="returnToIndex" view="externalRedirect:servletRelative:/index.jsp"> </end-state> </flow>
與清單 1 相比,在 view-state 元素中指定了 view 屬性的名字,這個名字也是 Spring Web MVC 中 viewResolver 所查找的 view 的名字。從清單 16 的配置中能夠知道,這三個 view-state 元素所對應的視圖資源分別應該是: viewCart.jsp 、 viewOrder.jsp 和 orderConfirmed.jsp 。清單 16 中最後的 end-state 指明瞭當 flow 執行結束後跳轉到初始的 index.jsp 頁面,在此處的 view 屬性的名字須要解釋一下。 externalRedirect 用在 view 名字中,表示所指向的資源是在 flow 的外部, servletRelative 則代表所指向資源的路徑起始部分與 flow 所在 servlet 相同。 Spring Web Flow 2.0還提供了其餘幾個關鍵詞用於重定向,這裏就很少介紹了。
在 /WEB-INF/jsp 目錄下建立三個 flow 所需的視圖資源。如下清單隻給出 jsp 頁面中 body 元素之內的代碼,其他省略。
<h1>View Cart</h1> <a href="${flowExecutionUrl}&_eventId=submit">Submit</a>
<h1>Order</h1> <a href="${flowExecutionUrl}&_eventId=confirm">Confirm</a>
<h1>Order Confirmed</h1> <a href="${flowExecutionUrl}&_eventId=returnToIndex">Return to index</a>
這幾個頁面都使用了變量 flowExecutionUrl ,表示 flow 執行到當前狀態時的 URL 。 flowExecutionUrl 的值已經由 Spring Web Flow 2.0 框架的代碼進行賦值,並放入相應的 model 中供 view 訪問。 flowExecutionUrl 的值包含 flow 在執行過程當中會爲每一狀態生成的惟一的 key ,所以不可用其餘手段來獲取。請求參數中 _eventId 的值與清單 16 中 transition 元素的 on 屬性的值是對應的,在接收到_eventId參數後,相應transition會被執行。
在 index.jsp 頁面中添加啓動 flow 的連接,從 webmvc-config.xml 配置文件中能夠看出,要啓動 flow ,只需提供 /shopping.do 連接便可。
<h1>Hello!</h1><br/> <a href="shopping.do">View Cart</a>
將應用程序發佈到 Tomcat 服務器,訪問 index.jsp ,並啓動 flow ,測試頁面的跳轉。效果如圖 5所示:
到如今爲止,這個購物車應用只是實現了頁面之間的跳轉,接下來咱們要實現與業務邏輯相關的功能。因爲本教程的重點在於介紹如何應用 Spring Web Flow ,所實現的業務比較簡單,與實際應用有較大的距離,請讀者諒解。
業務的邏輯涉及到數據的獲取、傳遞、保存,相關的業務功能函數的調用等內容,這些功能的實現均可用 Java 代碼來完成,但定義 Spring Web Flow 的語法與 Java 是無關的,這就要求 Spring Web Flow 提供與 Java 代碼的整合機制。要了解這種機制,關鍵在於搞清楚兩個問題:
在 Spring Web Flow 中,業務邏輯代碼的執行可由如下三種情形來觸發:
這種方式通常用在 state 之間的 transition ,經過指定 _eventId 參數的值,代表了客戶的行爲,從而致使相應事件的發生,在 Spring Web Flow 的定義文件中能夠經過 evaluate 元素來指定要處理的業務邏輯。參看清單21:
<transition on="submit"> <evaluate expression="validator.validate()" /> </transition>
清單 21 的代碼表示,當客戶端的請求中包含「 _eventId=submit 」,則 evaluate 元素中 expression 屬性所指明的表達式會被執行,即 validator 對象的validate 方法會獲得調用。
Spring Web Flow 定義了 5 個切入點,經過 flow 定義文件的配置,可在這 5 個切入點插入相關業務邏輯代碼。
切入點名稱 | XML 元素名稱 | 觸發時刻 |
---|---|---|
flow start | on-start | flow 執行以前 |
state entry | on-entry | 進入某個 state 以後,作其餘事情以前 |
view render | on-render | 在進入 view 的 render 流程以後,在 view 真正 render出來以前 |
state exit | on-exit | 在退出 state 以前 |
flow end | on-end | flow 執行結束以後 |
清單 22 給出了在 view render 切入點插入業務邏輯代碼的例子:
<view-state id="viewCart" view="viewCart" > <on-render> <evaluate expression="productService.getProducts()" result="viewScope.products"/> </on-render> </view-state>
Spring Web Flow 中的這個 <action-state> 是專爲執行業務邏輯而設的 state 。若是某個應用的業務邏輯代碼即不適合放在 transition 中由客戶端來觸發,也不適合放在 Spring Web Flow 自定義的切入點,那麼就能夠考慮添加 <action-state> 元素專用於該業務邏輯的執行。示例代碼參看清單23:
<action-state id="addToCart"> <evaluate expression="cart.addItem(productService.getProduct(productId))"/> <transition to="productAdded"/> </action-state>
Spring Web Flow 的定義中可直接使用表達式語言( Expression Language ),前面的代碼都是用的 Unified EL ,對於習慣用 OGNL 的開發人員,可經過 flow-builder-services 的配置改爲使用 OGNL 。無論是哪種表達式語言, Spring Web Flow 都提供了一些固定名稱的變量,用於數據的保存、傳遞。在 Spring Web Flow 的解決方案 一節中,已經提到 Spring Web Flow 所着力解決的問題便是數據存取範圍的問題,爲此, Spring Web Flow 提供了兩種比較重要的範圍,一是 flow 範圍,另外一個是 conversation 範圍。經過 flowScope 和 conversationScope 這兩個變量, Spring Web Flow 提供了在這兩種範圍裏存取數據的方法。清單 24演示瞭如何將業務邏輯代碼執行的結果存放到flow範圍中。
<evaluate expression="productService.getProducts()" result="flowScope.products" />
Spring Web Flow 2.0 在默認配置下,flowScope 和 conversationScope 的實現依賴於 Java 序列化和反序列化技術,所以存放於 flowScope 或 conversationScope 中的對象須要實現 java.io.Serializable 接口。
Spring Web Flow 還提供了大量其餘的變量,以方便數據的存取。如 viewScope 範圍便是從進入 view-state 至退出 view-state 結束, requestScope 即和通常的 request 範圍沒什麼區別,等等。另外還有一些用於獲取 flow 之外數據的變量,如 requestParameters 、 messageContext 等等。具體變量的列表可參看 Spring Web Flow自帶的文檔。
接下來,咱們要在示例應用的 viewCart.jsp 頁面中添加商品,可按如下步驟操做:
Product 類是個普通的 JavaBean ,用於定義商品( Product )的通常屬性,同時也提供了構造方法。因爲會把 Product 存放於 conversationScope 中, Product 實現了 Serializable 接口。具體見清單25:
package samples.webflow; import java.io.Serializable; public class Product implements Serializable { private static final long serialVersionUID = 1951520003958305899L; private int id; private String description; private int price; public Product(int id, String description, int price) { this.id = id; this.description = description; this.price = price; } /*省略getter和setter*/ }
ProductService 主要提供商品列表,並能根據商品的 id 查找出該商品,因爲示例較簡單,這裏只添加了三條紀錄。見清單 26:
package samples.webflow; /*省略import語句*/ @Service("productService") public class ProductService { /*products 用於存放多個商品 */ private Map<Integer, Product> products = new HashMap<Integer, Product>(); public ProductService() { products.put(1, new Product(1, "Bulldog", 1000)); products.put(2, new Product(2, "Chihuahua", 1500)); products.put(3, new Product(3, "Labrador", 2000)); } public List<Product> getProducts() { return new ArrayList<Product>(products.values()); } public Product getProduct(int productId) { return products.get(productId); } }
Service 註解表示 Spring IoC 容器會初始化一個名爲 productService 的 Bean ,這個 Bean 可在 Spring Web Flow 的定義中直接訪問。
要在 viewCart 頁面中顯示商品,只需在 view-state 元素的 on-render 切入點調用 productService 的 getProducts 方法,並將所得結果保存到 viewScope 中便可。見清單27:
<view-state id="viewCart" view="viewCart" > <on-render> <evaluate expression="productService.getProducts()" result="viewScope.products"/> </on-render> <transition on="submit" to="viewOrder"> </transition> </view-state>
清單 27 代表 productService 的 getProducts 方法所得的結果會存放在 viewScope 中名爲 products 的變量中, jsp 頁面的代碼可直接訪問該變量。見清單 28:
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>View Cart</title> </head> <body> <h1>View Cart</h1> <h2>Items in Your Cart</h2> <a href="${flowExecutionUrl}&_eventId=submit">Submit</a> <h2>Products for Your Choice</h2> <table> <c:forEach var="product" items="${products}"> <tr> <td>${product.description}</td> <td>${product.price}</td> </tr> </c:forEach> </table> </body> </html>
商品已經有列表了,接下來就要增長把商品放入購物車的功能,在本示例中用 subflow 來實現這一功能,操做步驟以下:
CartItem 表示存放於購物車中的條目,主要記錄相應商品及商品數量,同時不要忘記實現 java.io.Serializable 接口,見清單 29:
package samples.webflow; import java.io.Serializable; public class CartItem implements Serializable { private static final long serialVersionUID = 8388627124326126637L; private Product product; private int quantity; public CartItem(Product product, int quantity) { this.product = product; this.quantity = quantity; } public int getTotalPrice() { return this.quantity * this.product.getPrice(); } public void increaseQuantity() { this.quantity++; } /*省略getter和setter*/ }
除去相應的屬性外, CartItem 可根據商品的數量算出該商品的總價格( getTotalPrice ),也可經過 increaseQuantity 增長商品數量。
Cart 是購物車的實現類,其一樣要實現 java.io.Serializable 接口,但它沒有像 ProductService 同樣成爲由 Spring IoC 容器管理的 Bean ,每一個客戶的購物車是不一樣的,所以不能使用 Spring IoC 容器默認的 Singleton 模式。見清單 30:
package samples.webflow; /* 省略 import 語句 */ public class Cart implements Serializable { private static final long serialVersionUID = 7901330827203016310L; private Map<Integer, CartItem> map = new HashMap<Integer, CartItem>(); public List<CartItem> getItems() { return new ArrayList<CartItem>(map.values()); } public void addItem(Product product) { int id = product.getId(); CartItem item = map.get(id); if (item != null) item.increaseQuantity(); else map.put(id, new CartItem(product, 1)); } public int getTotalPrice() { int total = 0; for (CartItem item : map.values()) total += item.getProduct().getPrice() * item.getQuantity(); return total; } }
Cart 主要實現三個業務函數, getItems 用於獲取當前購物車裏的物品, addItem 用於向購物車添加商品, getTotalPrice 用於獲取購物車裏全部商品的總價格。
在 shopping flow 開始時必須分配一個 Cart 對象,因爲要調用 subflow ,這個 Cart 對象應存放於 conversationScope 中。同時要添加一個 subflow-state 用於執行添加商品到購物車的任務。
<var name="mycart" class="samples.webflow.Cart"/> <on-start> <set name="conversationScope.cart" value="mycart"></set> </on-start> <view-state id="viewCart" view="viewCart" > <on-render> <evaluate expression="productService.getProducts()" result="viewScope.products"/> </on-render> <transition on="submit" to="viewOrder"/> <transition on="addToCart" to="addProductToCart"/> </view-state> <subflow-state id="addProductToCart" subflow="addToCart"> <transition on="productAdded" to="viewCart" /> </subflow-state>
清單 31 中 subflow-state 元素的 subflow 屬性即指明瞭這個被調用的 flow 的 id 爲「 addToCart 」,如今就要添加addToCart flow的定義。
<?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"> <on-start> <set name="requestScope.productId" value="requestParameters.productId"/> </on-start> <action-state id="addToCart"> <evaluate expression="cart.addItem(productService.getProduct(productId))"/> <transition to="productAdded"/> </action-state> <end-state id="productAdded"/> </flow>
addToCart flow 主要由一個 action-state 構成,完成添加商品到購物車的功能, addToCart flow 的實現須要有輸入參數,即 productId 。在本示例中是經過請求參數來傳遞,經過 requestParameters 來獲取該數值。這裏還要注意到清單 32 中的 end-state 的 id 爲「 productAdded 」,與清單 31 中 subflow-state 中的 transition元素的on屬性的名稱是對應的。
新增長的 flow 不要忘記在 flow-registry 中註冊。
<webflow:flow-registry id="flowRegistry" flow-builder-services="flowBuilderServices"> <webflow:flow-location path="/WEB-INF/flows/shopping.xml" id="shopping"/> <webflow:flow-location path="/WEB-INF/flows/addToCart.xml" id="addToCart"/> </webflow:flow-registry>
最後就能夠來看在視圖中如何顯示相關的信息,並觸發相應的 webflow 事件,見清單 34:
<h1>View Cart</h1> <h2>Items in Your Cart</h2> <c:choose> <c:when test="${empty cart.items}"> <p>Your cart is empty.</p> </c:when> <c:otherwise> <table border="1" cellspacing="0"> <tr> <th>Item</th> <th>Quantity</th> <th>Unit Price</th> <th>Total</th> </tr> <c:forEach var="item" items="${cart.items}"> <tr> <td>${item.product.description}</td> <td>${item.quantity}</td> <td>${item.product.price}</td> <td>${item.totalPrice}</td> </tr> </c:forEach> <tr> <td>TOTAL:</td> <td></td> <td></td> <td>${cart.totalPrice}</td> </tr> </table> </c:otherwise> </c:choose> <a href="${flowExecutionUrl}&_eventId=submit">Submit</a> <h2>Products for Your Choice</h2> <table> <c:forEach var="product" items="${products}"> <tr> <td>${product.description}</td> <td>${product.price}</td> <td> <a href="${flowExecutionUrl}&_eventId=addToCart&productId=${product.id}">[add to cart]</a> </td> </tr> </c:forEach> </table>
顧名思義, global transition 是一種全局的 transition ,可在 flow 執行的各個 state 中被觸發。
<global-transitions> <transition on="cancelShopping" to="returnToIndex"/> </global-transitions>
客戶端請求中若是包含 _eventId=cancelShopping ,則會從新回到 index.jsp 頁面。
本教程經過一個簡單的實例演示了 Spring Web Flow 的基本功能,經過本教程的學習,讀者應當能對如何應用 Spring Web Flow 有了基本的瞭解。因爲 2.0 版本的參考資料較爲缺少,一些比較深刻的問題還有待進一步探討。