使用Spring來建立一個簡單的工做流引擎

  原文地址:    [url]http://www.javaworld.com/javaworld/jw-04-2005/jw-0411-spring.html[/url]
  中文地址:    [url]http://www.matrix.org.cn/resource/article/43/43785_Spring.html[/url]

   摘要
  spring是支持控制反轉編程機制的一個相對新的框架。本文把spring做爲簡單 工做 流引擎,將它用在了更加通用的地方。在對工做流簡單介紹以後,將要介紹在基本工做流場景中基於Spring的工做流API的 使用

  許多 J2EE應用程序要求在一個和主機分離的上下文中執行處理過程。在許多狀況下,這些後臺的進程執行多個任務,一些任務依賴於之前任務的狀態。因爲這些處理任務之間存在相互依賴的關係,使用一套基於過程的方法調用經常不能知足要求。 開發 人員可以利用Spring來容易地將後臺進程分離成活動的集合。Spring容器鏈接這些活動,並將它們組織成簡單的工做流。

  在本文中,簡單工做流被定義成不須要用戶干預,以必定順序執行的任意活動的集合。然而,咱們並不建議將這種方式代替存在的工做流框架。在一些場景中,須要更多的用戶交互,例如基於用戶輸入而進行的轉向,鏈接或傳輸,這時,比較好的方法是配用一個單獨的開源或者商業的工做流引擎。一個開源 項目 已經成功地將更復雜的工做流設計集成到spring中。

  若是你手上的工做流任務是簡單的,那麼,與功能完備的獨立工做流框架相比,簡單工做流的策略就會變得有意義,特別地,若是已經使用了spring,這種快速實現能夠保證 時間 不會變得更加漫長。此外,考慮到spring輕量級的控制反轉容器的特色,spring在資源負載上減小了資源負載。

  這篇文章簡短地從編程主題的角度介紹工做流。經過使用工做流的概念,spring被用來做爲 驅動工做流 引擎 的框架。而後,討論了生產部署選項。如今,讓咱們從工做流的設計模式和相關背景信息來介紹簡單工做流的思想吧。
 

簡單工做流
  工做流模型是一個早在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>

  將工做流組件實施成spring beans產生了兩個使人喜悅的結果,就是容易進行單元測試和很大程度上可重用能力。IoC容器的特色明顯地提供了有效的單元測試。使用像spring這樣的Ioc容器,在測試期間,協做者之間的依賴可以容易的用假的替代者替代。在這個航線的例子中,可以容易地從惟一的測試ApplicationContext中檢索出像activity5活動這樣的spring bean。用一個假的SMTP代理SMTP 服務器,就有可能單獨地測試activity5。

  第二個意外的結果,可重用能力是經過像XSL轉換這樣的工做流活動實現的。一個被抽象成工做流活動的XSL轉換如今可以被任何處理XSL轉換的工做流所重用。
 
裝配工做流
  在提供的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>

  SequenceProcessor類是一個對順序模式建模的具體子類。有5個活動被鏈接到工做流處理器,工做流處理器將順序執行這5個活動。

  與大多數過程式後臺進程相比,工做流的 解決方案真正的突出了高度強壯的錯誤處理。錯誤處理句柄能夠單獨地處理每一個活動。這種類型的句柄在單一活動級別提供了細緻的錯誤處理。若是沒有單獨處理單個活動的錯誤處理句柄,那麼全局工做流處理器的錯誤處理句柄將會處理出現的問題。例如,若是在工做流處理過程當中的任意時刻,一個沒有被處理的錯誤出現了,那麼它將會向外傳播,被使用defaultErrorHandler屬性裝配的ErrorHandler Bean處理。

  更復雜的工做流框架將工做流轉換之間的狀態持久化存儲到 數據庫中。在這篇文章中,咱們僅僅對狀態轉換是自動完成的工做流感興趣。狀態信息僅僅在實際工做流運行時在ProcessContext中獲得。在ProcessContext中,你僅僅能看到ProcessContext的接口的兩個方法:
public interface ProcessContext extends Serializable
{
public boolean stopProcess();
public void setSeedData(Object seedObject);
}

  用於航線例子工做流的具體的ProcessContext類是RateDropContext類。RateDropContext類封裝了用於執行航線費率下降工做流必須的數據。

  到如今爲止,經由缺省的ApplicationContext的做用,全部bean實例都已經成了單例。可是,對於每個航線工做流的調用,咱們必須建立一個新的RateDropContext類的實例。爲了處理這種需求,須要配置SequenceProcessor,採用全限定類名做爲processContextClass屬性的值。對於每一個工做流的執行,SequenceProcessor使用指定的類名從spring檢索ProcessorContext類的一個新的實例。爲了使這種機制可以起做用,非單件的spring bean或者類型org.iocworkflow.test.sequence.simple.SimpleContext的原型必須存在於ApplicationContext中。
 
播種工做流
  既然咱們知道怎樣使用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);
}

  大多數狀況下,工做流須要一些初始化激活才能開始。開始一個處理過程有兩個選項:doActivities(ObjectseedData)方法或者無參數的doActivities()。下面的代碼列表是包含在樣例代碼中爲SequenceProcessor而實現的doActivities():
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;
}
}

  在這個航空費用減小的例子中,工做流過程的種子數據包括航線信息和費率減小的信息。使用容易測試的航線工做流例子,經過doActivities(Object seedData)方法發出種子數據並激活一個單一的工做流過程是簡單的:
BaseProcessor processor = (BaseProcessor)context.getBean("rateDropProcessor");
processor.doActivities(createSeedData());

  這些代碼是從包含在這篇文章中的測試例子中摘錄的。rateDropProcessor Bean是從ApplicationContext中檢索來的。rateDropProcessor其實是裝配成SequenceProcessor的實例來處理順序執行。createSeedData()方法實例化一個對象,這個對象封裝了初始化航線工做流所須要的全部種子數據。
 
Processor選項
  雖然包含在源代碼中的Processor具體的子類僅僅是SequenceProcessor,可是,許多Processor接口的實現也是能夠想象獲得的。能夠開發其餘工做流處理過程子類來控制不一樣的工做流類型,例如,另外一種像並行切割模式那樣有着變化的執行路徑的工做流。對於簡單工做流來講,由於活動的順序是預先決定了的,因此SequenceProcessor是好的選擇。儘管沒有被包括進來,對於使用基於spring的簡單工做流的實現來講,排他選擇模式是另外一個好的選擇。當使用排他選擇模式時,在每一個活動執行以後,Processor具體類就會訊問ProcessorContext,接下來將要執行哪個活動。

  注:有關並行切割,排他選擇和其餘工做流模式的更多信息,請參看W.M.P. van der Aalst等人寫的《工做流模式》一書。

啓動工做流
  考慮到工做流過程經常須要異步執行的特色,使用分離的執行線程來啓動工做流就變得有意義了。對於工做流的異步啓動而言,有好幾個選項;咱們主要集中在其中的兩個:積極地檢測(actively polling)一個隊列來啓動工做流,或者使用經過ESB(ent ERPrise service bus, 企業服務總線)的事件 驅動方式來啓動工做流,而Mule就是ESB的一個開源項目。

  圖3和圖4描繪了兩種啓動策略。圖3中,積極檢測在工做流中第一個活動常常檢查資源的情形下發生,好比數據源或POP3郵件賬戶。若是圖3中的積極檢測發現有任務等待處理,那麼啓動就會開始。
圖 3. 經過積極檢測來啓動工做流
  另外一方面,圖4表示了使用JMS( Java消息服務)的 J2EE應用程序把事件放到隊列上的情形。一個經過ESB配置的事件監聽器收到圖4中的事件,而且開始工做流,這樣,啓動工做流過程。
圖 4. 經過ESB事件來啓動工做流
  使用所提供樣例的代碼,讓咱們更詳細的看看主動選擇啓動方式與事件驅動的啓動方式。
 
積極檢測
  積極檢測是一種花費較少的啓動工做流過程的方案。SequenceProcessor足夠靈活,以使得可以經過平滑的選擇工做來進行啓動過程。儘管並不使人滿意,在沒有時間進行事件 驅動子系統的配置和部署的許多情景中,積極檢測是明智的選擇。

  使用Spring的ScheduledTimerTask,檢測模式就可以容易地裝配。缺點就是必須建立額外的活動來進行檢測。這個檢測活動必須被設計來訊問某些實體,如 數據庫表,pop郵件賬戶,或者 Web服務,而後決定新的工做是否等待參與到工做流中。

  在所提供的例子中,PollingTestCase類實例化一個基於檢測的工做流過程。使用一個有着積極檢測處理過程與事件驅動的啓動過程的不一樣之處在於,spring支持doActivities()方法的無參數版本。相反地,在事件驅動的啓動中,啓動處理過程的實體經過doActivities(Object seedData)方法提供了種子數據來啓動工做流。檢測方法的另外一個缺點是:資源不必定可以被重複地使用。依賴於應用程序環境,這種資源的消耗是不可接受的。

  下面代碼例子演示了使用積極檢測來控制工做流啓動的一個活動:
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;
}
}

  此外,包含在例子代碼的單元測試中的PollRates類提供了一個主動選舉啓動的能夠運行的例子。PollRates模擬了對於航線費率降低的重複檢查。

經過ESB的事件驅動啓動工做流
  理想地,一個包含了適當的種子數據的線程可以異步地啓動工做流。這種狀況的一個例子是收到從 Java消息服務隊列的消息。一個監聽JMS隊列或者主題的客戶會收到通知,這個通知告知處理應該在onMessage()方法中開始工做流。而後,經過使用Spring和doActivities(Object seedData)方法就可以得到工做流處理器Bean。

  使用ESB,實際用於發送啓動事件的機制可以恰當地從工做流處理器中分離出來。開源項目Mule ESB有緊湊地和Spring相集成的好處。任意傳送機制,好比JMS,JVM,或者POP3郵箱都可以發起事件的傳播。

工做流的連續運行
  工做流引擎後臺進程應該可以沒有干擾地連續運行。對於正在運行的基於spring的工做流單一進程來講好,有幾個選項。一個有着main()方法的簡單Java類就足夠演示與這篇文章伴隨着的單元測試中的例子了。一個更加可靠的用於部署的機制是嵌入工做流到某種形式的 J2EE組件中。Spring很好地支持和J2EE兼容的web應用程序歸檔或者war文件的集成。基於Java管理附件(JMX)服務歸檔和JBoss應用 服務器(更多信息,參見JBoss homepage)支持的sar文件是更加合適的可部署組件,這種更合適的可部署組件也可以被用來將部署歸檔。在JBoss 4.0中,sar文件已經被你們所知道的deployer的格式所取代了。

例子代碼
  打包成zip格式的例程代碼最好是用Apache Maven來使用它們。你可以在主源代碼目錄src/java找到API。src/java目錄中有三個單元測試,包括:SimpleSequenceTestCase,RateDropTestCase和PoolingTestCase。要運行全部這些測試,在命令行shell中鍵入maven test,而後在編譯和運行以前,Maven將會 下載全部必需的jar文件。實際的XSL轉換將會發生在兩個測試中,它們的結果被管道輸出到控制檯。鍵入maven test:ui來拉出圖形化的測試運行器,而後選擇你想要運行的測試,而且觀察控制檯的結果。

結論   在這篇文章中你已經經過設計模式看到了工做流過程種類,在這些模式中,咱們主要集中介紹了順序模式。經過使用接口,咱們來對基本工做流組件建模。經過裝配多個接口實現到Spring,實現一個順序工做流。最後還討論了啓動和部署工做流的不一樣選項。   這裏所提出的簡單工做流技術確定不是最終的和革命性的。可是,使用Spring來實現像工做流這樣的通用任務是一個經過使用IoC容器而得到的效率的好的示例。因爲減小了粘合性代碼的須要,Spring在保持面向對象的約束同時,減小面向對象操做麻煩的程度。
相關文章
相關標籤/搜索