八、BPMN 2.0流程圖詳解 BPMN 2.0的標準的出現是好事,用戶不在被某個工作流開發商綁架或者在工作流中開發妥協,Activiti作為BPMN標準的一套解決方案,使得用戶在選擇工作流框架時可以平滑的遷移過渡。也有負面的不好的消息,就是BPMN標準是大量開會討論和開發商妥協的結果(一般這是在做 ...
八、BPMN 2.0流程圖詳解
BPMN 2.0的標準的出現是好事,用戶不在被某個工作流開發商綁架或者在工作流中開發妥協,Activiti作為BPMN標準的一套解決方案,使得用戶在選擇工作流框架時可以平滑的遷移過渡。也有負面的不好的消息,就是BPMN標準是大量開會討論和開發商妥協的結果(一般這是在做夢),所以用戶在閱讀BPMN規範會感覺到它太笨重了,Activiti開發工作流將用戶體驗放到第一位置,開發出了工作流設計插件。工作流官方推薦使用工作流設計插件。
8.1 事件(Event)
每個流程設計都有start event和end event,而在整個流程中發生的事件都是有event來表示。事件在設計面板中用圓圈表示,在BPMN 2.0中主要有兩種事件:
- Catching:當流程執行到事件的時候, 它會等待被觸發。而觸發條件需要用戶配置在這個圓圈圖標的屬性裡面,和下麵第二種圓圈圖標外形上的區別是:Catching圖標裡面是空的,就是空圈。
- Throwing:當流程執行到事件的時候,它會立即觸發,同樣的觸發器也需要配置在圖標屬性裡面,和Catching圖標不同是圓圈圖標裡面有東西是,黑色的。
總的來說,事件定義決定了事件的語義。如果沒有事件定義,這個事件就不做什麼特別的事情。 沒有設置事件定義的開始事件不會在啟動流程時做任何事情。如果給開始事件添加了一個事件定義 (比如定時器事件定義)我們就聲明瞭開始流程的事件 "類型 " (這時定時器事件監聽器會在某個時間被觸發)。
8.1.1 定時器事件
定時器事件是根據指定的時間觸發的事件。可以用於開始事件(start event), 中間事件(intermediate event)和邊界事件(boundary event)。定時器事件必須含有下麵一種屬性的配置。
timeDate:指定ISO 8601格式的日期定時器激活。(至於ISO 8601日期格式可以詳見百度:http://baike.baidu.com/view/931641.htm)
<timerEventDefinition> <timeDate>2016-08-23T18:13:00</timeDate> </timerEventDefinition>
timeDuration:定義定時器經過多少時間後激活。時間段也是取得ISO 8601格式,比如在一年三個月五天六小時七分三十秒內,可以寫成P1Y3M5DT6H7M30S。
<timerEventDefinition> <timeDuration>P10D</timeDuration> </timerEventDefinition>
timeCycle:定義定時器重覆間隔,在某些場景使用,比如周期性的啟動流程,任務超時發送提醒。timeCycle的設置目前有兩種方式:ISO 8601和Cron表達式(quartz任務調度框架提供的解決方案),activiti預設是使用ISO 8601。例如現在重覆三次,每次間隔10小時:
1 <timerEventDefinition> 2 <timeCycle activiti:endDate="2016-08-22T16:42:11+00:00">R3/PT10H</timeCycle> 3 </timerEventDefinition>
<timerEventDefinition> <timeCycle>R3/PT10H/${EndDate}</timeCycle> </timerEventDefinition>
其中endDate是可選的配置,上面使用了兩張方式加上了endDate, 定時器將會在指定的時間停止工作。
此外如果你使用Cron 表達式,可以這樣寫:
0 0/5 * * * ?
註意: 第一個數字表示秒,而不是像通常Unix cron中那樣表示分鐘。重覆的時間周期能更好的處理相對時間,它可以計算一些特定的時間點 (比如用戶任務的開始時間),而cron表達式可以處理絕對時間, 這對定時啟動事件特別有用。
你可以使用表達式進行配置,在裡面動態設置值,不過該值需要為ISO 8601或者(cron表達式)格式,
<boundaryEvent id="escalationTimer" cancelActivity="true" attachedToRef="firstLineSupport"> <timerEventDefinition> <timeDuration>${duration}</timeDuration> </timerEventDefinition> </boundaryEvent>
定義器的執行有先決條件:async executor和job被啟用定時器才會激活(例如在activiti.cfg.xml中配置了jobExecutorActivate或者asyncExecutorActivate為true)。
8.1.2 錯誤事件定時器
BPMN的錯誤是關於業務上面的異常處理,它和java代碼上的異常是不同的,兩者完全不同,比如這樣配置一個錯誤事件:
1 <endEvent id="myErrorEndEvent"> 2 <errorEventDefinition errorRef="myError" /> 3 </endEvent>
8.1.3 信號事件
信號事件會引用一個命名的信號,所謂的信號作用在整個流程引擎全局範圍內,會發送給所有活躍的處理器。信號事件在BPMN文件中是定義在signalEventDefinition中,其中的signalRef屬性可以引用前面聲明的signal,而signal在definitions的根節點中作為子元素,下麵就是一個例子
1 <definitions... > 2 <!-- 聲明signal --> 3 <signal id="alertSignal" name="alert" /> 4 5 <process id="catchSignal"> 6 <intermediateThrowEvent id="throwSignalEvent" name="Alert"> 7 <!-- signal event definition --> 8 <signalEventDefinition signalRef="alertSignal" /> 9 </intermediateThrowEvent> 10 ... 11 <intermediateCatchEvent id="catchSignalEvent" name="On Alert"> 12 <!-- signal event definition --> 13 <signalEventDefinition signalRef="alertSignal" /> 14 </intermediateCatchEvent> 15 ... 16 </process> 17 </definitions>
Throwing信號事件:在BPMN中配置或者用代碼實現都可以發出信號,而使用代碼可以這樣子:
1 RuntimeService.signalEventReceived(String signalName); 2 RuntimeService.signalEventReceived(String signalName, String executionId);
這兩個方法不同之處在於第一個方法發出全局的信號,第二個方法會指定execution發出信號。
Catching信號事件:被中間事件和邊界事件捕獲的事件。
前面第二個方法的executionId或者查詢當前活躍的信號事件方法如下:
1 List<Execution> executions = runtimeService.createExecutionQuery() 2 .signalEventSubscriptionName("alert") 3 .list();
信號的作用範圍:
預設的信號作用域是整個流程引擎,也就是說你可以throw一個信號在多個流程實例之間併發生作用。有時候我們需要作用範圍僅僅是在發生事件的流程實例里,限制信號的作用範圍,可以這樣配置,不過它並不是BPMN2.0規範中的,是activiti獨有的,其中activiti:scope的預設值是global。
1 <signal id="alertSignal" name="alert" activiti:scope="processInstance"/>
信號事件案例:這裡我使用了Activiti Explorer線上流程圖設計器設計了兩張圖,展示了信號交互。
第一張流程是從保險規則變動開始的,然後相關人員審批,如果同意後會發出保險條件發生改變的信號。
第二張流程中將在紅框標識的地方會捕獲(Catching)這個事件,使得保險合同在這時重新計算。
信號是通過廣播傳遞給所有活躍的事件,但有時候我們並不是想要這種結果,譬如下圖:
上面流程圖的意思是執行“do something”任務時出現的錯誤,會被邊界錯誤事件捕獲, 然後使用信號傳播給併發路徑上的分支,進而中斷"do something inparallel"任務, 但是,根據信號的廣播含義,它也會傳播給所有其他訂閱了信號事件的流程實例,這就是我們不想要的。這時我們需要調用前面介紹觸發信號的API的第二個方法進行手動關聯。
8.1.4 消息事件
消息事件會引用已命名的消息。和信號不同的是,消息具有名稱和內容,並且消息始終指定了單個的接收者。
消息事件定義在BPMN文件的messageEventDefinition元素中,其中messageRef屬性值來自於message,至於message是配置在definitions的根元素裡面。下麵是一個例子:
<definitions id="definitions" xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:activiti="http://activiti.org/bpmn" targetNamespace="Examples" xmlns:tns="Examples"> <message id="newInvoice" name="newInvoiceMessage" /> <message id="payment" name="paymentMessage" /> <process id="invoiceProcess"> <startEvent id="messageStart" > <messageEventDefinition messageRef="newInvoice" /> </startEvent> ... <intermediateCatchEvent id="paymentEvt" > <messageEventDefinition messageRef="payment" /> </intermediateCatchEvent> ... </process> </definitions>
拋出消息事件:Activiti作為嵌入式的引擎,它不會關註怎麼接收消息,接收消息取決於你的環境和特定的平臺,比如你可以連接到JMS消息隊列或者執行WebService或REST請求,這是需要你的應用層架構中進行實現,Activiti只是其中一部分。
在你的應用裡面收到消息,你需要處理它,如果是啟動流程實例的消息,可以參考下麵的API:
1 ProcessInstance startProcessInstanceByMessage(String messageName); 2 ProcessInstance startProcessInstanceByMessage(String messageName, Map<String, Object> processVariables); 3 ProcessInstance startProcessInstanceByMessage(String messageName, String businessKey, Map<String, Object> processVariables);
這些API允許使用引用的消息進行啟動流程實例。如果流程實例需要接收這些消息,首先你需要關聯指定流程實例和消息,然後觸發處於等待的流程,使用RunTimeService可以觸發基於消息的流程。
1 void messageEventReceived(String messageName, String executionId); 2 void messageEventReceived(String messageName, String executionId, HashMap<String, Object> processVariables);
查詢訂閱消息事件的流程定義:
對於start event的消息,消息事件關聯到指定的流程定義,消息的訂閱可以使用ProcessDefinitionQuery查詢。
1 ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery() 2 .messageEventSubscription("newCallCenterBooking") 3 .singleResult();
對於明確的消息是對應一個流程的,所以查詢結果一般是0個或者1個,如果是流程定義更新,那麼方法返回最新的流程定義。
如果是中間消息事件,訂閱的消息關聯到特定的流程,我們可以使用ExecutionQuery進行查詢:
1 Execution execution = runtimeService.createExecutionQuery() 2 .messageEventSubscriptionName("paymentReceived") 3 .variableValueEquals("orderId", message.getOrderId()) 4 .singleResult();
下麵的實例通過兩個不同的消息進行啟動流程實例:
在某些需要多個start event啟動流程實例需要統一的處理方式的時候是有用處的。