jBPM4實(shí)現(xiàn)基本活動(dòng)(下)
5.5. 基本流程執(zhí)行
在下一個(gè)例子里,我們會(huì)結(jié)合自動(dòng)活動(dòng)和等待狀態(tài)。 這里例子構(gòu)建了貸款審批流程,使用WaitState 和Display活動(dòng),我們剛剛創(chuàng)建的。 貸款流程的圖形看起來像這樣:
貸款流程
圖 5.3. 貸款流程
使用Java構(gòu)建流程圖形是很乏味的事情, 因?yàn)槟惚仨氃诰植孔兞恐懈櫵械囊谩?為了解決這個(gè)問題,流程虛擬機(jī)提供了一個(gè)ProcessFactory。 ProcessFactory是一種領(lǐng)域特定語言(DSL),可以嵌入到Java中, 簡化流程圖形的結(jié)構(gòu)。這個(gè)模型也叫做 流暢接口。
- ClientProcessDefinition processDefinition = ProcessFactory.build("loan")
- .activity("submit loan request").initial().behaviour(new Display("loan request submitted"))
- .transition().to("evaluate")
- .activity("evaluate").behaviour(new WaitState())
- .transition("approve").to("wire money")
- .transition("reject").to("end")
- .activity("wire money").behaviour(new Display("wire the money"))
- .transition().to("archive")
- .activity("archive").behaviour(new WaitState())
- .transition().to("end")
- .activity("end").behaviour(new WaitState())
- .done();
為了了解ProcessFactory的更多細(xì)節(jié),可以參考 api文檔。 ProcessFactory的另一種選擇是創(chuàng)建一個(gè)XML語言和一個(gè)XML解析器,來表示流程。 XML解析器可以直接實(shí)例化 org.jbpm.pvm.internal.model包中的類。 這種方式一般都被流程語言選擇使用。
初始化活動(dòng)submit loan request和 wire the money活動(dòng)是自動(dòng)活動(dòng)。 在這個(gè)例子中,wire the money活動(dòng)的 Display實(shí)現(xiàn) 使用Java API來把信息輸出到控制臺(tái)上。但是讀取器可以想象一個(gè)可選的 Activity實(shí)現(xiàn),使用支付流程庫的Java API 來實(shí)現(xiàn)一個(gè)真實(shí)的自動(dòng)支付。
上述流程的一個(gè)新執(zhí)行可以像下面這樣啟動(dòng)
ClientExecution execution = processDefinition.startProcessInstance();
當(dāng)startExecution方法返回時(shí), submit loan request活動(dòng)會(huì)被執(zhí)行, 執(zhí)行會(huì)位于evaluate活動(dòng)。
位于'evaluate'活動(dòng)的執(zhí)行
圖 5.4. 位于'evaluate'活動(dòng)的執(zhí)行
現(xiàn)在,執(zhí)行處在一個(gè)很有趣的點(diǎn)。這里有兩個(gè)轉(zhuǎn)移從evaluate指向外邊。 一個(gè)轉(zhuǎn)移叫approve 一個(gè)轉(zhuǎn)移叫reject。像我們上面解釋的, WaitState實(shí)現(xiàn)會(huì)根據(jù)執(zhí)行的signal選擇轉(zhuǎn)移。 讓我們像這樣執(zhí)行'approve' signal:
execution.signal("approve");
這個(gè)approve signal會(huì)導(dǎo)致執(zhí)行選擇approve轉(zhuǎn)移 它會(huì)到達(dá)wire money活動(dòng)。
在wire money活動(dòng)中,信息會(huì)打印到控制臺(tái)里。 因?yàn)镈isplay沒有調(diào)用execution.waitForSignal(), 也沒有調(diào)用其他執(zhí)行傳播方法, 默認(rèn)流程行為只會(huì)讓執(zhí)行繼續(xù), 使用向外的轉(zhuǎn)移到達(dá)archive活動(dòng), 這也是一個(gè)WaitState。
位于'archive'活動(dòng)的執(zhí)行
圖 5.5. 位于'archive'活動(dòng)的執(zhí)行
所以只有當(dāng)archive到達(dá)時(shí), signal("approve")會(huì)返回。
另一個(gè)signal就像這樣:
- execution.signal("approve");
將讓執(zhí)行最終到達(dá)結(jié)束狀態(tài)。
位于'end'活動(dòng)的執(zhí)行
圖 5.6. 位于'end'活動(dòng)的執(zhí)行
5.6. 事件
事件位于流程定義中, 一系列的EventListener可以進(jìn)行注冊。
- public interface EventListener extends Serializable {
- void notify(EventListenerExecution execution) throws Exception;
- }
事件的目的是讓開發(fā)者可以為流程添加程序邏輯, 不必改變流程圖。 這是非常有價(jià)值的機(jī)制,可以促進(jìn)業(yè)務(wù)分析人員和開發(fā)者之間的協(xié)作。 業(yè)務(wù)分析人員負(fù)責(zé)描述需求。 當(dāng)他們使用流程圖歸檔那些需求, 開發(fā)者可以獲得這些圖形,讓它可執(zhí)行化。 事件會(huì)非常方便,向一個(gè)流程中添加技術(shù)細(xì)節(jié)(比如一些數(shù)據(jù)庫插入操作) 這些都是業(yè)務(wù)分析人員不感興趣的東西。
最常用的事件是由執(zhí)行自動(dòng)觸發(fā)的:
TODO: 在用戶手冊中解釋事件
事件是由流程元素和事件名稱結(jié)合而成。 用戶和流程語言也可以出發(fā)事件, 使用編程的方式在流程中使用fire方法。
- public interface Execution extends Serializable {
- ...
- void fire(String eventName, ProcessElement eventSource);
- ...
- }
可以把一系列的EventListeners分配給一個(gè)事件。 但是事件監(jiān)聽器不能控制執(zhí)行的流向, 因?yàn)樗鼈儍H僅是監(jiān)聽已經(jīng)執(zhí)行了的執(zhí)行。 這與活動(dòng)處理活動(dòng)的行為是不同的。 活動(dòng)行為可以響應(yīng)執(zhí)行的傳播。
我們會(huì)創(chuàng)建一個(gè)PrintLn事件監(jiān)聽器, 這與上面的Display活動(dòng)是非常相似的。
- public class PrintLn implements EventListener {
- String message;
- public PrintLn(String message) {
- this.message = message;
- }
- public void notify(EventListenerExecution execution) throws Exception {
- System.out.println("message");
- }
- }
多個(gè)PrintLn監(jiān)聽器 會(huì)在流程中注冊。
PrintLn監(jiān)聽器流程
圖 5.7. PrintLn監(jiān)聽器流程
- ClientProcessDefinition processDefinition = ProcessFactory.build()
- .activity("a").initial().behaviour(new AutomaticActivity())
- .event("end")
- .listener(new PrintLn("leaving a"))
- .listener(new PrintLn("second message while leaving a"))
- .transition().to("b")
- .listener(new PrintLn("taking transition"))
- .activity("b").behaviour(new WaitState())
- .event("start")
- .listener(new PrintLn("entering b"))
- .done();
***個(gè)事件演示如何為相同的事件注冊多個(gè)監(jiān)聽器。 它們會(huì)根據(jù)它們指定的順序依次執(zhí)行。
然后,在轉(zhuǎn)椅上,這里的事件只有一種類型。 所以在那種情況下,事件類型不需要指定, 監(jiān)聽器可以直接添加到轉(zhuǎn)移上。
一個(gè)監(jiān)聽器每次都會(huì)執(zhí)行,當(dāng)一個(gè)執(zhí)行觸發(fā)事件時(shí),如果這個(gè)監(jiān)聽器被注冊了。 執(zhí)行會(huì)作為一個(gè)參數(shù)提供給活動(dòng)接口, 除了控制流程傳播的方法以外, 都可以被監(jiān)聽器使用。
5.7. 事件傳播
事件會(huì)默認(rèn)傳播給最近的流程元素。 目的是允許監(jiān)聽器在流程定義或組合活動(dòng)中 可以執(zhí)行所有發(fā)生在流程元素中的事件。 比如這個(gè)功能允許為end事件在流程定義或一個(gè)組合活動(dòng)中注冊一個(gè)事件監(jiān)聽器。 這種動(dòng)作會(huì)被執(zhí)行,如果一個(gè)活動(dòng)離開。 如果事件監(jiān)聽器被注冊到一個(gè)組合活動(dòng)中, 它也會(huì)被所有活動(dòng)執(zhí)行,當(dāng)組合活動(dòng)中出現(xiàn)了離開事件。
為了清楚地顯示這個(gè),我們會(huì)創(chuàng)建一個(gè)DisplaySource事件監(jiān)聽器, 這會(huì)把leaving信息和事件源 打印到控制臺(tái)。
- public class DisplaySource implements EventListener {
- public void execute(EventListenerExecution execution) {
- System.out.println("leaving "+execution.getEventSource());
- }
- }
注意事件監(jiān)聽器的目的不是可視化,這是為什么事件監(jiān)聽器本身 不應(yīng)該顯示在圖形中。一個(gè)DisplaySource事件監(jiān)聽器 會(huì)作為end事件的監(jiān)聽器添加到組合活動(dòng)中。
下一個(gè)流程展示了DisplaySource事件監(jiān)聽器如何 作為'end'事件的監(jiān)聽器注冊到composite活動(dòng):
一個(gè)在組合活動(dòng)中為end事件注冊了不可見的事件監(jiān)聽器的流程。
圖 5.8. 一個(gè)在組合活動(dòng)中為end事件注冊了不可見的事件監(jiān)聽器的流程。
TODO 更新代碼片段
下一步,我們會(huì)啟動(dòng)一個(gè)執(zhí)行。
ClientExecution execution = processDefinition.startProcessInstance();
在啟動(dòng)一個(gè)新執(zhí)行后,執(zhí)行將在a活動(dòng)中 作為初始活動(dòng)。沒有活動(dòng)離開,所以沒有信息被記錄下來。 下一個(gè)signal會(huì)給與執(zhí)行, 導(dǎo)致它選擇從a到b。
execution.signal();
當(dāng)signal方法返回,執(zhí)行會(huì)選擇轉(zhuǎn)移 然后end事件會(huì)被a活動(dòng)觸發(fā)。 那個(gè)組合活動(dòng)會(huì)被傳播到組合活動(dòng)和流程定義中。 因?yàn)槲覀兊腄isplaySource 監(jiān)聽器放到 composite活動(dòng)中, 它會(huì)接收事件,把下面的信息打印到控制臺(tái)中:
leaving activity(a)
另一個(gè)
execution.signal();
會(huì)選擇b到c的轉(zhuǎn)移。那會(huì)觸發(fā)兩個(gè)活動(dòng)離開事件。 一個(gè)在b活動(dòng),一個(gè)在組合活動(dòng)。 所以下面的幾行會(huì)添加到控制臺(tái)輸出中:
leaving activity(b)
leaving activity(composite)
事件傳播建立在流程定義的繼承組合結(jié)構(gòu)中。 ***元素總是流程定義。 流程定義包含一系列活動(dòng)。每個(gè)活動(dòng)可以是葉子活動(dòng)或者可以是一個(gè)組合節(jié)點(diǎn), 這意味著它包含了一系列內(nèi)嵌活動(dòng)。 內(nèi)嵌活動(dòng)可以被使用,比如超級狀態(tài)或組合活動(dòng),在內(nèi)嵌流程語言中,像BPEL。
所以事件模型在組合活動(dòng)和上面的流程定義中的功能是相似的。 想象'Phase one'模型一個(gè)超級狀態(tài)作為一個(gè)狀態(tài)機(jī)。 然后事件傳播允許在超級狀態(tài)中注冊所有事件。 這個(gè)主意是繼承組合響應(yīng)圖形展示。 如果一個(gè)'e'元素畫在另一個(gè)'p'元素中, 'p'是'e'的父節(jié)點(diǎn)。一個(gè)流程定義擁有一系列定義活動(dòng)。 每個(gè)活動(dòng)可以擁有一系列內(nèi)嵌活動(dòng)。 一個(gè)轉(zhuǎn)移的父節(jié)點(diǎn)就是它的源頭和目的的***個(gè)父節(jié)點(diǎn)。
如果一個(gè)事件監(jiān)聽器對傳播的事件沒有興趣, 可以在構(gòu)建流程使用ProcessFactory的propagationDisabled()。 下一個(gè)流程是與上面相同的流程, 除了傳播的事件會(huì)被事件監(jiān)聽器禁用。 圖形還是一樣。
注冊到'end'事件的事件監(jiān)聽器被禁用的流程。
圖 5.9. 注冊到'end'事件的事件監(jiān)聽器被禁用的流程。
使用流程工廠構(gòu)建流程:
TODO 更新代碼
所以當(dāng)***個(gè)signal在流程中調(diào)用時(shí),end事件 會(huì)再次觸發(fā)在a活動(dòng)上,但是現(xiàn)在在組合活動(dòng)的事件監(jiān)聽器 不會(huì)被執(zhí)行,因?yàn)閭鞑サ氖录唤昧恕?禁用傳播是單獨(dú)的事件監(jiān)聽器的一個(gè)屬性, 不會(huì)影響其他監(jiān)聽器。事件會(huì)一直被觸發(fā), 傳播到整個(gè)父繼承結(jié)構(gòu)。
ClientExecution execution = processDefinition.startProcessInstance();
***個(gè)signal會(huì)選擇從a到b的流程。 沒有信息會(huì)被打印到控制臺(tái)。
execution.signal();
下一步,第二個(gè)signal會(huì)選擇從b到c的轉(zhuǎn)移。
execution.signal()
還是兩個(gè)end事件被觸發(fā), 就像上面分別在b和composite活動(dòng)中。 ***個(gè)事件是b活動(dòng)上的 end事件。 那將被傳播給composite活動(dòng)。 所以事件監(jiān)聽器不會(huì)為這個(gè)事件執(zhí)行,因?yàn)樗呀?jīng)禁用了傳播。 但是事件監(jiān)聽器會(huì)在composite活動(dòng)上 為end事件執(zhí)行。 那是不傳播的,但是直接在composite活動(dòng)上觸發(fā)。 所以事件監(jiān)聽器現(xiàn)在會(huì)被執(zhí)行 一次,為組合活動(dòng),就像下面控制臺(tái)里顯示的那樣:
leaving activity(composite)
【編輯推薦】