自定義擴(kuò)展
- BPMN 2.0標(biāo)準(zhǔn)對(duì)于各方都是一個(gè)好東西
- 用戶不用擔(dān)心會(huì)綁死在供應(yīng)商提供的專有解決方案上
- 框架,特別是activiti這樣的開(kāi)源框架,可以提供相同功能,甚至是更好的實(shí)現(xiàn),足以和大的供應(yīng)商媲美
- 按照BPMN 2.0標(biāo)準(zhǔn),從大供應(yīng)商的解決方案遷移到activiti只要經(jīng)過(guò)一個(gè)簡(jiǎn)單而平滑的過(guò)程
- BPMN 2.0標(biāo)準(zhǔn)不好的一點(diǎn)是
- 它常常是不同公司之間大量討論和妥協(xié)的結(jié)果
- 作為開(kāi)發(fā)者去閱讀流程定義的BPMN 2.0xml時(shí),有時(shí)會(huì)感覺(jué)用這種結(jié)構(gòu)和方法去做事太麻煩了
- 因此activiti把簡(jiǎn)化開(kāi)發(fā)作為最優(yōu)先的事情,使用一些被稱為Activiti BPMN擴(kuò)展的功能,這些擴(kuò)展是新的結(jié)構(gòu)或方法來(lái)簡(jiǎn)化對(duì)應(yīng)的結(jié)構(gòu),并不屬于BPMN 2.0規(guī)范
-
根據(jù)BPMN 2.0標(biāo)準(zhǔn)開(kāi)發(fā)自定義擴(kuò)展的注意點(diǎn):
- 自定義擴(kuò)展的前提是總有簡(jiǎn)單的方法轉(zhuǎn)換成標(biāo)準(zhǔn)方法. 所以使用自定義擴(kuò)展時(shí),可以及時(shí)撤銷自定義擴(kuò)展
- 當(dāng)使用自定義擴(kuò)展時(shí) ,總會(huì)清楚的指明使用了新的XML元素,屬性... 比如會(huì)使用activiti:命名空間前綴
- 擴(kuò)展的目標(biāo)是最終加入到下一版本的BPMN規(guī)范,或者至少可以引起對(duì)特定BPMN結(jié)構(gòu)的討論
事件
- 事件用來(lái)表明流程的生命周期中發(fā)生了什么事. 事件總是畫(huà)成一個(gè)圓圈
- 在BPMN 2.0中,事件有兩大分類:捕獲(catching)事件 或觸發(fā)(throwing)事件:
- 捕獲(catching): 當(dāng)流程執(zhí)行到事件,會(huì)等待被觸發(fā).觸發(fā)的類型是由內(nèi)部圖表或XML中的類型聲明來(lái)決定的.捕獲事件與觸發(fā)事件在顯示方面是根據(jù)內(nèi)部圖表是否被填充來(lái)區(qū)分的(白色)
- 觸發(fā)(throwing): 當(dāng)流程執(zhí)行到事件,會(huì)觸發(fā)一個(gè)事件.觸發(fā)的類型是由內(nèi)部圖表或XML中的類型聲明來(lái)決定的.觸發(fā)事件與捕獲事件在顯示方面是根據(jù)內(nèi)部圖表是否被填充來(lái)區(qū)分的(黑色)
事件定義
- 事件定義決定了事件的語(yǔ)義. 如果沒(méi)有事件定義,這個(gè)事件就不做什么特別的事情.沒(méi)有設(shè)置事件定義的開(kāi)始事件不會(huì)在啟動(dòng)流程時(shí)做任何事情
- 如果給開(kāi)始事件添加了一個(gè)事件定義(比如定時(shí)器事件定義)我們就聲明了開(kāi)始流程的事件類型(這時(shí)定時(shí)器事件監(jiān)聽(tīng)器會(huì)在某個(gè)時(shí)間被觸發(fā))
定時(shí)器事件定義
- 定時(shí)器事件是根據(jù)指定的時(shí)間觸發(fā)的事件
- 定時(shí)器事件可以用于開(kāi)始事件,中間事件和邊界事件
- ==定時(shí)器定義元素:==
- timeDate: 觸發(fā)事件的時(shí)間. 使用ISO8601格式指定的一個(gè)確定的時(shí)間:
<timerEventDefinition>
<timeDate>2011-03-11T12:13:14</timeDate>
</timerEventDefinition>
- timeDuration: 指定定時(shí)器之前要等待多長(zhǎng)時(shí)間. timeDuration可以設(shè)置為timerEventDefinition的子元素,使用ISO8601規(guī)定的格式
<timerEventDefinition>
<!--等待10天-->
<timeDuration>P10D</timeDuration>
</timerEventDefinition>
-
timeCycle: 指定重復(fù)執(zhí)行的間隔. 可以用來(lái)定期啟動(dòng)流程實(shí)例,或?yàn)槌瑫r(shí)時(shí)間發(fā)送多個(gè)提醒.timeCycle元素可以使用兩種格式:
- ISO8601標(biāo)準(zhǔn)的格式
- cron表達(dá)式
<timerEventDefinition>
<!--重復(fù)3次,每次間隔10小時(shí)-->
<timeCycle>R3/PT10H</timeCycle>
</timerEventDefinition>
- 從整點(diǎn)開(kāi)始,每5分鐘執(zhí)行一次:
0 0/5 * * * ?
- ==注意:==
- 第一個(gè)數(shù)字表示秒, 而不是像通常Unix cron中那樣表示分鐘
- 重復(fù)的時(shí)間周期能更好的處理相對(duì)時(shí)間, 可以計(jì)算一些特定的時(shí)間點(diǎn):用戶任務(wù)的開(kāi)始時(shí)間
- cron表達(dá)式可以處理絕對(duì)時(shí)間, 這對(duì)定時(shí)啟動(dòng)事件特別有用
- 在定時(shí)器事件定義中使用表達(dá)式,可以通過(guò)流程變量來(lái)影響那個(gè)定時(shí)器定義: 流程定義必須包含ISO8601(或者cron)格式的字符串,以匹配對(duì)應(yīng)的時(shí)間類型
<boundaryEvent id="escalationTimer" cancelActivity="true" attachedToRef="firstLineSupport">
<timerEventDefinition>
<timeDuration>${duration}</timeDuration>
</timerEventDefinition>
</boundaryEvent>
只有啟用job執(zhí)行器之后,定時(shí)器才會(huì)被觸發(fā).activiti.cfg.xml中的jobExecutorActivate需要設(shè)置為true, 默認(rèn)job執(zhí)行器是關(guān)閉的
錯(cuò)誤事件定義
- 錯(cuò)誤事件是由指定錯(cuò)誤觸發(fā)的
- ==注意:==
- BPMN錯(cuò)誤與Java異常完全不一樣:
- BPMN錯(cuò)誤事件是為了對(duì)業(yè)務(wù)異常建模
- Java異常是要用特定方式處理
- BPMN錯(cuò)誤與Java異常完全不一樣:
- 錯(cuò)誤事件定義會(huì)引用一個(gè)error元素,引用相同error元素的錯(cuò)誤事件處理器會(huì)捕獲這個(gè)錯(cuò)誤
<endEvent id="myErrorEndEvent">
<!--引用一個(gè)錯(cuò)誤聲明-->
<errorEventDefinition errorRef="myError" />
</endEvent>
信號(hào)事件定義
- 信號(hào)事件會(huì)引用一個(gè)已命名的信號(hào)
- 信號(hào)全局范圍的事件(廣播語(yǔ)義).會(huì)發(fā)送給所有激活的處理器
- 信號(hào)事件定義使用signalEventDefinition元素 .signalRef屬性會(huì)引用definitions根節(jié)點(diǎn)里定義的signal子元素(signalEventDefinition引用相同的signal元素)
<!--流程實(shí)例,其中會(huì)拋出一個(gè)信號(hào),并被中間事件捕獲-->
<definitions... >
<!-- declaration of the signal -->
<signal id="alertSignal" name="alert" />
<process id="catchSignal">
<intermediateThrowEvent id="throwSignalEvent" name="Alert">
<!-- signal event definition -->
<signalEventDefinition signalRef="alertSignal" />
</intermediateThrowEvent>
...
<intermediateCatchEvent id="catchSignalEvent" name="On Alert">
<!-- signal event definition -->
<signalEventDefinition signalRef="alertSignal" />
</intermediateCatchEvent>
...
</process>
</definitions>
觸發(fā)信號(hào)事件
- 可以通過(guò)bpmn節(jié)點(diǎn)由流程實(shí)例觸發(fā)一個(gè)信號(hào).也可以通過(guò)API觸發(fā)
- org.activiti.engine.RuntimeService中的方法可以用來(lái)手工觸發(fā)一個(gè)信號(hào):
RuntimeService.signalEventReceived(String signalName);
RuntimeService.signalEventReceived(String signalName, String executionId);
- signalEventReceived(String signalName): 把信號(hào)發(fā)送給全局所有訂閱的處理(廣播語(yǔ)義)
- signalEventReceived(String signalName, String executionId): 把信號(hào)發(fā)送給指定的執(zhí)行
捕獲信號(hào)事件
- 信號(hào)事件可以被中間信號(hào)事件或邊界信息事件捕獲
查詢信號(hào)事件的訂閱
- 查詢所有訂閱特定信號(hào)事件的執(zhí)行
List<Execution> executions = runtimeService.createExecutionQuery()
.signalEventSubscriptionName("alert")
.list();
- 使用signalEventReceived(String signalName, String executionId) 把信號(hào)發(fā)送給這些執(zhí)行
信號(hào)事件范圍
- 默認(rèn)情況下,信號(hào)會(huì)在流程引擎范圍內(nèi)進(jìn)行廣播: 在一個(gè)流程實(shí)例中拋出一個(gè)信號(hào)事件,其他不同流程定義的流程實(shí)例都可以監(jiān)聽(tīng)到這個(gè)事件
- 有時(shí)只要在同一個(gè)流程實(shí)例中響應(yīng)這個(gè)信號(hào)事件:流程實(shí)例中的同步機(jī)制,如果兩個(gè)或更多活動(dòng)是互斥的
- 要想限制信號(hào)事件的范圍, 可以使用信號(hào)事件定義的scope屬性:
<signal id="alertSignal" name="alert" activiti:scope"processInstance"/>
默認(rèn)情況下,scope的屬性為global
信號(hào)事件實(shí)例
- 不同流程使用信號(hào)交互:
-
流程在保險(xiǎn)規(guī)則更新或改變時(shí)啟動(dòng).在修改被參與者處理時(shí),會(huì)觸發(fā)一個(gè)信號(hào),通知規(guī)則改變:
在這里插入圖片描述
這個(gè)事件會(huì)被所有相關(guān)的流程實(shí)例捕獲 -
訂閱這個(gè)事件的流程實(shí)例:
- - 信號(hào)事件是廣播給所有激活的處理器的
- 在上面的例子中,所有流程實(shí)例都會(huì)接收到這個(gè)事件,這就是我們想要的.
-
然而,有的情況下并不想要這種廣播行為,考慮下面的流程:
在這里插入圖片描述
上述流程描述的模式activiti并不支持.這種想法是:
- 執(zhí)行[do something]任務(wù)時(shí)出現(xiàn)的錯(cuò)誤
- 被邊界錯(cuò)誤事件捕獲
- 然后使用信號(hào)傳播給并發(fā)路徑上的分支
- 進(jìn)而中斷[do something inparallel]任務(wù)
- 目前,activiti實(shí)際運(yùn)行的結(jié)果與期望一致.信號(hào)會(huì)傳播給邊界事件并中斷任務(wù).但是,根據(jù)信號(hào)的廣播含義,也會(huì)傳播給所有其他訂閱了信號(hào)事件的流程實(shí)例.所以,這就不是我們想要的結(jié)果
- ==注意:==
- 信號(hào)事件不會(huì)執(zhí)行任何與特定流程實(shí)例的聯(lián)系
- 如果只想把一個(gè)信息發(fā)給指定的流程實(shí)例,需要手工關(guān)聯(lián),再使用 signalEventReceived(String signalName, String executionId) 和對(duì)應(yīng)的查詢機(jī)制
消息事件定義
- 消息事件會(huì)引用一個(gè)命名的消息,每個(gè)消息都有名稱和內(nèi)容
- 消息事件總會(huì)直接發(fā)送給一個(gè)接受者
- 消息事件定義使用messageEventDefinition元素.messageRef屬性引用了definitions根節(jié)點(diǎn)下的一個(gè)message子元素:
<!--使用兩個(gè)消息事件的流程例子,開(kāi)始事件和中間捕獲事件分別聲明和引用了兩個(gè)消息事件-->
<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>
觸發(fā)消息事件
- 作為一個(gè)嵌入式的流程引擎,activiti不能真正接收一個(gè)消息
- 這些環(huán)境相關(guān),與平臺(tái)相關(guān)的活動(dòng):比如連接到JMS(Java消息服務(wù))隊(duì)列或主題或執(zhí)行WebService或REST請(qǐng)求. 這個(gè)消息的接收是你要在應(yīng)用或架構(gòu)的一層實(shí)現(xiàn)的,流程引擎則內(nèi)嵌其中
- 在應(yīng)用接收一個(gè)消息之后,必須決定如何處理它:
- 如果消息應(yīng)該觸發(fā)啟動(dòng)一個(gè)新流程實(shí)例,在下面的RuntimeService的兩個(gè)方法中選擇一個(gè)執(zhí)行:
ProcessInstance startProcessInstanceByMessage(String messageName);
ProcessInstance startProcessInstanceByMessage(String messageName, Map<String, Object> processVariables);
ProcessInstance startProcessInstanceByMessage(String messageName, String businessKey, Map<String, Object> processVariables);
這些方法允許使用對(duì)應(yīng)的消息系統(tǒng)流程實(shí)例
- 如果消息需要被運(yùn)行中的流程實(shí)例處理:
- 首先要根據(jù)消息找到對(duì)應(yīng)的流程實(shí)例
- 然后觸發(fā)這個(gè)等待中的流程
- RuntimeService提供了可以基于消息事件的訂閱來(lái)觸發(fā)流程繼續(xù)執(zhí)行:
void messageEventReceived(String messageName, String executionId);
void messageEventReceived(String messageName, String executionId, HashMap<String, Object> processVariables);
查詢消息事件訂閱
- Activiti支持開(kāi)始消息事件和中間消息事件
- 消息開(kāi)始事件: 消息事件訂閱分配給一個(gè)特定的process definition. 這個(gè)消息訂閱可以使用ProcessDefinitionQuery查詢到
ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
.messageEventSubscription("newCallCenterBooking")
.singleResult();
因?yàn)橥瑫r(shí)只能有一個(gè)流程定義關(guān)聯(lián)到消息的訂閱點(diǎn),查詢總是返回0或一個(gè)結(jié)果.如果流程定義更新了,那么只有最新版本的流程定義會(huì)訂閱到消息事件上
- 中間捕獲消息事件: 消息事件訂閱會(huì)分配給特定的執(zhí)行,這個(gè)消息事件訂閱可以使用ExecutionQuery查詢到:
Execution execution = runtimeService.createExecutionQuery()
.messageEventSubscriptionName("paymentReceived")
.variableValueEquals("orderId", message.getOrderId())
.singleResult();
這個(gè)查詢可以調(diào)用對(duì)應(yīng)的查詢,通常是流程相關(guān)的信息 :最多只能有一個(gè)流程實(shí)例對(duì)應(yīng)著orderId
消息事件實(shí)例
-
使用兩個(gè)不同消息啟動(dòng)的流程實(shí)例:
在這里插入圖片描述 - 消息事件可以用于流程需要不同的方式來(lái)區(qū)分開(kāi)始事件時(shí),最終會(huì)進(jìn)入同樣的路徑