SPRING+WEBFLOW入门教程.docx
《SPRING+WEBFLOW入门教程.docx》由会员分享,可在线阅读,更多相关《SPRING+WEBFLOW入门教程.docx(14页珍藏版)》请在冰点文库上搜索。
SPRING+WEBFLOW入门教程
介绍
你是否觉得当你的Web应用越来越复杂,理解和管理由用例编排而成的页面流程也越来越困难了呢?
而被迫使用特定的方式做事情并且无法重用是不是让你感觉很累?
你是否觉得使用了太多时间开发你自己特定的方法去解决普遍问题就像会话状态管理?
进入SpringWebFlow。
什么是SpringWebFlow?
SpringWebFlow(SWF)是SpringFramework的一个脱离模块。
这个模块是SpringWeb应用开发模块栈的一部分,SpringWeb包含SpringMVC。
SpringWebFlow的目标是成为管理Web应用页面流程的最佳方案。
当你的应用需要复杂的导航控制,例如向导,在一个比较大的事务过程中去指导用户经过一连串的步骤的时候,SWF将会是一个功能强大的控制器。
以下是一个受控制的导航的例子,使用UML状态图描述:
图1-一个航空订票服务流程
聪明的读者会认出这个是一个典型的航空订票流程,就是那种每次你通过在线方式参与订票的过程。
为什么出现SpringWebFlow?
在传统的Web应用中,页面流程就像上面所展示的,不是很明确—他们不是一等公民。
就拿一个使用Struts的Web应用举个例子,为了在Struts里面实现页面流,大多数开发人员利用了框架提供的Action和视图。
在这种情况下,一个单独的Action就和一个指定的请求URL产生了联系。
只有当请求从那个URL过来的时候,Action才会被执行。
在执行过程中,Action运行一些处理并且选择一个合适的视图显示结果。
这非常简单。
所以要在Struts中实现多步控制的页面流,你需要通过不同的视图将独立的Action形成链。
用来处理不同事件,例如“后退”或“提交”的ActionURL都是硬编码在视图中的。
一些ad-hoc形式的会话存储被用来管理流程状态。
提交后重定向被用来阻止重复提交,等等。
虽然这是一个简单并且有效的方法,但是它具有一个很大的缺陷:
从struts-config.xml文件的Action定义中不能清晰的看到页面流程。
你无法从几棵树看到一片森林,就像你无法从Action和视图的定义看到页面流程一样。
灵活性也因为Action和视图不能被重用而大打折扣。
最后,你仍然需要做很多工作,这并不见得容易。
SpringMVC提供了一个轻便的高层次的功能:
表单控制器实现了一个与定义的页面流程。
它提供了两个这样的控制器:
SimpleFormController和AbstractWizardController。
尽管如此,这些仍然是大多数页面流程控制概念的例子。
Tapestry和JSP在页面的基础上而不是请求的基础上使用事件驱动的方法,使得每个页面和它的后退控制器逻辑保持一致。
然而,仍然需要提供一个优秀的类根据一个定义良好的能跨越多个页面和不同路径的生存周期去支持一个逻辑页面流程。
就如你所看到的,这个页面流程的生存周期要比单一的请求长,但是却比一个会话要短。
这就是SpringWebFlow的切入点,允许你使用一个简单清晰的方法体现你的页面流程,并且随时重用,包括像Struts、SpringMVC、Tapestry、JSP甚至Portlets这些环境下。
优点
正如你所看到的,SpringWebFlow提供以下优点:
∙Web应用中的页面流程可以通过Web流程的定义(XML文件或者Java类)清晰的展现出来。
∙Web流程被设计成自包含的。
这就允许你把你的应用中的一部分看作是一个模块,这样就你可以在多种场合重用它。
∙Web流程捕获任何合理的页面流程总是使用同种技术。
你不必被迫在特定的场合使用特定的控制器。
∙最后,Web流程是一等公民并且可以通过一个良好定义的契约使用。
它具有一个清晰的,可观察的生存周期为你自动管理。
通过简单配置,系统便会为你管理复杂的逻辑,总而言之,这非常容易使用。
SpringWebFlow是如何工作的?
现在已经有能力说Web流程是一组状态(states)的集合。
一个状态是流程中发生某事的一个点。
举个例子,譬如显示一个视图或者执行一个Action。
每个状态都有一个或更多的转变(transitions)用来移动到下一个状态。
一个转变是由一个事件(event)触发的。
航空订票Web流程示例
为了展示一个Web流程的定义,下面的XML片段展示了上面UML状态图定义的航空订票处理:
xmlversion="1.0"encoding="UTF-8"?
>
DOCTYPEwebflowPUBLIC"-//SPRING//DTDWEBFLOW//EN"
"http:
//www.springframework.org/dtd/spring-webflow.dtd">
then="enterPassengerInformation"else="displayReservationVerification"/>
图2-基于XML的航空订票流程定义
就像你所看到的,仅仅是扫过XML定义,逻辑流程驱动的订票流程处理就已经可以清晰地辨认出来了,即使你都不了解SpringWebFlow实现细节。
如果你看得仔细点,你将会发现两个子流程产生了订票流程的子过程。
第一个子流程指导用户输入乘客信息。
第二个让用户分配他的座位。
这个内嵌的流程扮演了“迷你应用程序模块”的角色,这是SpringWebFlow强大的功能之一。
你可以将这份定义上交给一位业务分析人员,并且她估计能看懂。
更好的是你可以根据这个定义绘制一个可视化图表将其提交给业务分析人员。
做这个的工具已经诞生了。
航空订票流程祥解
这篇文章的下一部分将逐块分解上面的航空订票流程定义,并且提供对话框演示SpringWebFlow是如何工作的。
流程定义
这是第一行的XML流程定义:
...
webflow元素定义了流程,指定它的id和start-state。
id是一个简单的唯一的标识符,start-state是一个转变的初始状态,这发生在当一个新的流程会话在运行时被激活的时候。
所以,在业务案例上,当订票会话被激活的时候,它将转变为obtainTripInfo状态。
获得行程信息行为状态(ActionState)
下面是obtainTripInfo状态定义。
记得当状态被进入,针对该状态的行为就发生了。
正如你将看到的,不同的状态类型有不同的执行动作。
actionstate,正如obtainTripInfo,在进入的时候执行一个Action。
该Action返回执行的逻辑结果,并且这个结果被映射到状态转变上。
一切就是这么简单。
所以,在这个业务案例上,obtainTripInfo,当进入的时候执行bookingActions这个Action的bindAndValidate方法。
这个方法从浏览器绑定表单输入到一个Trip领域对象并且检验它。
如果处理成功,就进入suggestItineraries状态。
如果错误发生,进入tryAgain状态。
订票Action
当在SpringIoC中使用SpringWebFlow的时候,action元素的bean属性涉及到SpringApplicationContext中定义的一个相同名称的Action实现。
下面是bookingActions的定义:
web-context.xml
class="org.springframework.samples.bookflight.BookingActions">
这就允许我们的Action实现被Spring管理并且通过依赖注入进行配置。
建议路线行为状态
现在我们看一下下一个ActionState,给定一个绑定的并且通过检验的Trip对象作为输入,返回一个建议的路线集合:
下面是Action实现代码:
publicclassBookingActionsextendsFormAction{
...
publicEventsuggestItineraries(RequestContextcontext){
Triptrip=(Trip)context.getRequestScope().getAttribute("trip");
Collectionitineraries=bookingAgent.suggestItineraries(trip);
context.getRequestScope().setAttribute("itineraries",itineraries);
returnsuccess();
}
}
当进入suggestItineraries状态的时候,suggestItineraries就被调用了。
其他的ActionState也是同样的工作方式:
进入状态并调用指定的方法。
显示建议路线视图状态(ViewState)
一旦返回了一个建议的路线集合,下一步就是让用户看到它们并且让用户选择他最喜欢的。
这可以通过以下的状态定义完成:
就如你所看到的,displaySuggestedItineraries是一个viewstate-一个我们还未讨论过的状态类型。
一个视图状态,当进入的时候,导致执行流程暂停,并将控制返回给客户端同时根据配置的视图同时返回。
随后,在用户思考过后,客户端发出一个事件描述用户执行的Action。
继续流程,事件的发生已经映射到了一个状态的转变,这个转变把用户带到了流程的下一步。
所以,在这个业务案例上,当进入displaySuggestedItineraries的时候suggestedIteneraries视图被渲染并且将控制返回给浏览器。
然后用户选择路线之后点击“选择”按钮。
这就出发了select事件,传递选择的路线id作为事件参数。
用户也可能选择startOver,这时候流程转变到了取消状态。
对于view属性,SpringMVC中,FlowControoler使用熟悉的ModelAndView和ViewResolver构造,在Struts中,FlowAction用ActionForward。
客户端状态
在这个问题上你可能会问:
“…自从进入ViewState之后,执行流程暂停了,控制返回给了浏览器,那么流程如何重新拾起并且继续运行呢?
”
答案就是客户端跟踪一个唯一的id用户标示流程执行点,并且将这个id放在input标签内,以便引起下一个事件。
典型的做法是放在一个隐藏域内。
举个例子,在一个JSP文件里:
outvalue="${flowExecution.id}"/>">
“是否需要乘客信息?
”决策状态(DecisionState)
用户选择了她需要的路线之后,流程需要做一个上下文关系(contextual)的决策关于下一步执行什么。
需要特别指出的是,如果用户没有登录,或者她已经登录但是希望确认她的信息-例如她所使用的信用卡-流程控制需要允许她确定这些信息。
另一方面,如果她已经登录并且希望直接进入预定页面,流程控制应该跳个这个可选步骤。
基本上需要做一个动态的决策重新考虑她的信息和偏好的。
决策状态最适合这个,看下面的定义:
then="enterPassengerInformation"else="displayReservationVerification"/>
输入乘客信息子流程状态(SubFlowState)
处理乘客信息的过程逻辑上独立的。
这是处理的一部分,但是在机票预定这个上下文环境之外它也可以独立存在。
子流程(Subflow)状态机制就是针对这个实现的。
当进入一个子流程状态,这个子流程就被产生了。
父流程挂起知道子流程结束。
这让你可以把你的应用作为一系列自包含的模块看待,至于流程,你可以很容易的把多种情况统一处理。
看一下enterPassengerInformation子流程状态:
flow属性是这个进入这个流程的id,attribute-mapper元素从子流程映射属性。
输入映射将属性向下映射到子流程。
输出映射将属性倒退会父流程当子流程结束的时候。
你可以从这里看到表达式也是支持的。
所以,在这个业务用例上,当进入enterPassengerInformation状态,乘客流程就产生了。
passengerId属性传递给这个流程作为输入。
从这里,自流程作它需要做的。
对于父流程来说这是一个黑箱。
当子流程结束,父流程继续,应答最后结果并决定去哪执行下一步—在这里,去确认预定。
显示确认结束状态(EndState)
最有一个状态类型在这里讨论:
结束状态。
当进入结束状态,活动的流程会话就结束了。
在结束上面,所有与之相关的资源都被自动清理。
displayConfirmation结束状态在一条路线被被成功预定后显示确认信息:
当进入这个状态的时候,订票流程结束了并且显示reservationConfirmation视图。
因为订票流程是根流程,并非子流程,所以任何分配的资源都会被自动清理。
注意:
结束流程如果是一个子流程,进入这个状态就会被认为是一个子流程结果并继续父流程。
更特别的是,这个状态的ID在继续父流程的子流程的状态上被用作一个状态的转变。
你可以从enterPassengerInformation子流程状态定义上看出来。
注意它如何响应子流程的“完成”结果,是通过一个“完成”结束状态。
流程部署
到这里,你了解了SpringWebFlow是关于什么的,并且你也看到了一个现实的例子。
现在你要看到的就是如何部署这个流程定义到特定的环境中去执行,就行SpringMVC在一个Servlet环境下一样:
做这事是很容易的,这里你需要和SpringMVC一起使用:
bookflight-flow.xml"/>
这就自动将bookingFlow导出至/booking.htm这个URL在一个Servlet环境里。
高级主题
下面的部分介绍了一些SWF更高级的特性。
流程执行监听器(FlowExecutionListeners)
FlowExecutionListener构造了一个观察者允许你监听并且对一个执行着的流程的生存周期作出反应。
你可以使用这个特性作任何事,从一个状态的预处理到后期条件的检测,或则审计、安全处理。
流程执行存储策略(FlowExecutionStorageStrategies)
一个执行着的流程的状态的存储机制是完全可插拔的。
基于HttpSession的存储是默认的,但是SWF提供两种其他的存储方式:
一个是使用服务器端连续的会话储存,另一种是使用完全的客户端序列化。
定义你自己的存储方式,举个例子,譬如使用数据库存储,是不推荐的。
什么时候使用Spri