摘要
spring是支持控制反轉編程機制的一個相對新的框架。本文把spring作為簡單工作流引擎,將它用在了更加通用的地方。在對工作流簡單介紹之后,將要介紹在基本工作流場景中基于Spring的工作流API的使用。
許多J2EE應用程序要求在一個和主機分離的上下文中執行處理過程。在許多情況下,這些后臺的進程執行多個任務,一些任務依賴于以前任務的狀態。由于這些處理任務之間存在相互依賴的關系,使用一套基于過程的方法調用常常不能滿足要求。開發人員能夠利用Spring來容易地將后臺進程分離成活動的集合。Spring容器連接這些活動,并將它們組織成簡單的工作流。
在本文中,簡單工作流被定義成不需要用戶干預,以一定順序執行的任意活動的集合。然而,我們并不建議將這種方式代替存在的工作流框架。在一些場景中,需要更多的用戶交互,例如基于用戶輸入而進行的轉向,連接或傳輸,這時,比較好的方法是配用一個單獨的開源或者商業的工作流引擎。一個開源項目已經成功地將更復雜的工作流設計集成到spring中。
如果你手上的工作流任務是簡單的,那么,與功能完備的獨立工作流框架相比,簡單工作流的策略就會變得有意義,特別地,如果已經使用了spring,這種快速實現可以保證時間不會變得更加漫長。此外,考慮到spring輕量級的控制反轉容器的特點,spring在資源負載上減少了資源負載。
這篇文章簡短地從編程主題的角度介紹工作流。通過使用工作流的概念,spring被用來作為驅動工作流引擎的框架。然后,討論了生產部署選項?,F在,讓我們從工作流的設計模式和相關背景信息來介紹簡單工作流的思想吧。
簡單工作流
工作流模型是一個早在70年代就有人開始研究的主題,許多開發者都試圖創建工作流模型規范。W.H.M. van der Aalst等人寫了《工作流模型》白皮書(2003年7月),它成功地提煉出一組設計模式,這些設計模式準確地將大多數通用的工作流場景建模。當中,最普通的工作流模式是順序模式 (Sequence pattern)。順序工作流模式滿足了簡單工作流的設計原則,并且由一組順序執行的活動組成。
UML(統一建模語言)活動圖通常被用來作為一個機制對工作流建模。圖1顯示了一個基本的使用標準UML活動圖對順序工作流過程的建模過程。
圖 1順序工作流模式
順序工作流是一個在J2EE中流行的標準工作流模式。J2EE應用程序在后臺線程中,通常需要一些順序發生的事件或者異步事件。圖2中的活動圖描述了一個簡單的工作流,用來通知感興趣的旅行者,他們感興趣的目的地的機票價格已經下降的事件。
圖 2.機票價格下降的簡單工作流
圖1中的航線工作流負責創建和發送動態的email通知。過程中的每一步表示了一個活動(activity)。在工作流處于活動之前,一些額外事件必須發生。在這個例子中,事件是飛行路線費率的減少。
讓我們來簡要的看一下航線工作流的業務邏輯。如果第一個活動找不到對費率減少通知感興趣的用戶,那么整個工作流就被取消。如果發現了感興趣的用戶,那么接下來的活動繼續執行。隨后,一個XSL(擴展樣式表)轉換生成消息內容,之后,記錄審計信息 (audit information)。最后,工作流試圖通過SMTP服務器發送這個消息。如果這個任務沒有錯誤地完成,便在日志中記錄成功的信息,進程結束。但是,如果在和SMTP服務器通訊時發生了錯誤,一個特別的錯誤處理例程將要管理這些錯誤。錯誤處理代碼將會試著去重新發送消息。
考慮這個航線的例子,一個明顯的問題是:你怎么樣有效地將順序處理過程分解為單獨的活動?這個問題被spring巧妙的處理了。下面,讓我們快速地討論spring的反轉控制框架。
控制反轉
Spring通過使用spring容器來負責控制對象之間的依賴關系,使得我們不再對對象之間的依賴負責。 這種依賴關系的實現就是大家所知道的控制反轉(IoC)或依賴注射。參見Martin Fowler's "Inversion of Control Containers and the Dependency Injection Pattern"(martinfowler.com, 2004年2月)得到關于控制反轉和依賴注射的更加深入的討論。通過管理對象之間的依賴關系,spring就不需要那些只是為了使類能夠相互協作,而將對象粘合的代碼。
作為spring beans的工作流組件
在進一步討論之前,現在是簡要介紹spring中主要概念的恰當時候。接口ApplicationContext是從接口BeanFactory繼承的,它被用來作為在spring容器內實際的控制實體和容器。
ApplicationContext負責對一組作為spring beans的一組bean的初始化,配置和生命期管理。我們通過裝配在一個基于XML的配置文件中的spring beans來配置ApplicationContext。這個配置文件說明了spring beans互相協作的本質特點。這樣,用spring的術語來說,與其他spring beans交互的spring beans就被叫著協作者(collaborators)。缺省情況下,spring beans是作為單例存在于ApplicationContext中的,但是,單例的屬性能夠被設置為false,從而有效地改變他們在spring中調用原型模式時的行為。
回到我們的例子,在飛機票價下降的時候,一個SMTP發送例程的抽象就被裝配在工作流過程例子中的最后的活動(例子代碼可以在 Resources中得到)。由于是第5個活動,我們命名它為activity5。要發送消息,activity5就要求一個代理協作者和一個錯位處理句柄。
<bean id="activity5" class="org.iocworkflow.test.sequence.ratedrop.SendMessage"> <property name="delegate"> <ref bean="smtpSenderDelegate"></ref> </property> <property name="errorHandler"> <ref bean="mailErrorHandler"/> </property> </bean>
裝配工作流
在提供的API中(從Resources下載),spring控制了一些操作者以一種工作流的方式交互。關鍵接口如下:
Activity: 封裝了工作流中一個單步業務邏輯
ProcessContext:在工作流活動之間傳遞具有ProcessContext類型的對象。實現了這個接口的對象負責維護對象在工作流轉換中從一個活動轉換到另一個活動的狀態。
ErrorHandler: 提供錯誤處理的回調方法。
Processor: 描述一個作為主工作流線程的執行者的bean。
下面從例子源碼中摘錄的代碼是將航線例子裝配為簡單工作流過程的spring bean的配置。
<!-- Airline rate drop as a simple sequence workflow process -->
<bean id="rateDropProcessor" class="org.iocworkflow.SequenceProcessor" >
<property name="activities">
<list>
<ref bean="activity1"/><!--Build recipients-->
<ref bean="activity2"/><!--Construct DOM tree-->
<ref bean="activity3"/><!--Apply XSL Transform-->
<ref bean="activity4"/><!--Write Audit Data-->
<ref bean="activity5"/><!--Attempt to send message-->
</list>
</property>
<property name="defaultErrorHandler">
<ref bean="defaultErrorHandler"></ref>
</property>
<property name="processContextClass">
<value>org.iocworkflow.test.sequence.ratedrop.RateDropContext</value>
</property>
</bean>
public interface ProcessContext extends Serializable
{
public boolean stopProcess();
public void setSeedData(Object seedObject);
}
播種工作流
既然我們知道怎樣使用spring來組裝一個簡單的工作流,就讓我們集中精力使用種子數據(seed data)示例工作流的過程。要明白怎樣開始工作流,看一看在實際接口Processor上表現的方法:
public interface Processor
{
public boolean supports(Activity activity);
public void doActivities();
public void doActivities(Object seedData);
public void setActivities(List activities);
public void setDefaultErrorHandler(ErrorHandler defaultErrorHandler);
}
public void doActivities(Object seedData)
{
//Retrieve injected by Spring
List activities = getActivities();
//Retrieve a new instance of the Workflow ProcessContext
ProcessContext context = createContext();
if (seedData != null)
context.setSeedData(seedData);
//Execute each activity in sequential order
for (Iterator it = activities.iterator(); it.hasNext();)
{
Activity activity = (Activity) it.next();
try {
context = activity.execute(context);
}
catch (Throwable th) {
//Determine if an error handler is available at the activity level
ErrorHandler errorHandler = activity.getErrorHandler();
if (errorHandler == null) {
getDefaultErrorHandler().handleError(context, th);
break;
}
else {
//Handle error using default handler
errorHandler.handleError(context, th);
}
}
//Ensure it's ok to continue the process
if (processShouldStop(context, activity))
break;
}
}
BaseProcessor processor = (BaseProcessor)context.getBean("rateDropProcessor");
processor.doActivities(createSeedData());
Processor選項
雖然包含在源代碼中的Processor具體的子類僅僅是SequenceProcessor,但是,許多Processor接口的實現也是可以想象得到的。可以開發其他工作流處理過程子類來控制不同的工作流類型,例如,另一種像并行切割模式那樣有著變化的執行路徑的工作流。對于簡單工作流來說,因為活動的順序是預先決定了的,所以SequenceProcessor是好的選擇。盡管沒有被包括進來,對于使用基于spring的簡單工作流的實現來說,排他選擇模式是另一個好的選擇。當使用排他選擇模式時,在每個活動執行之后,Processor具體類就會訊問ProcessorContext,接下來將要執行哪一個活動。
注:有關并行切割,排他選擇和其他工作流模式的更多信息,請參看W.M.P. van der Aalst等人寫的《工作流模式》一書。
啟動工作流
考慮到工作流過程常常需要異步執行的特點,使用分離的執行線程來啟動工作流就變得有意義了。對于工作流的異步啟動而言,有好幾個選項;我們主要集中在其中的兩個:積極地檢測(actively polling)一個隊列來啟動工作流,或者使用通過ESB(enterprise service bus, 企業服務總線)的事件驅動方式來啟動工作流,而Mule就是ESB的一個開源項目。
圖3和圖4描繪了兩種啟動策略。圖3中,積極檢測在工作流中第一個活動經常檢查資源的情形下發生,比如數據源或POP3郵件帳戶。如果圖3中的積極檢測發現有任務等待處理,那么啟動就會開始。
圖 3. 通過積極檢測來啟動工作流
另一方面,圖4表示了使用JMS(JAVA消息服務)的J2EE應用程序把事件放到隊列上的情形。一個通過ESB配置的事件監聽器收到圖4中的事件,并且開始工作流,這樣,啟動工作流過程。
圖 4. 通過ESB事件來啟動工作流
使用所提供樣例的代碼,讓我們更詳細的看看主動選擇啟動方式與事件驅動的啟動方式。
public class PollForWork implements Activity
{
public ProcessContext execute(ProcessContext context) throws Exception
{
//First check if work needs to be done
boolean workIsReady = lookIntoDatabaseForWork();
if (workIsReady)
{
//The Polling Action must also load any seed data
((MyContext) context).setSeedData(createSeedData());
}
else
{
//Nothing to do, terminate the workflow process for this iteration
((MyContext) context).setStopEntireProcess(true);
}
return context;
}
}
新聞熱點
疑難解答