工作流框架引擎之消息事件流詳解

自定義擴(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異常是要用特定方式處理
  • 錯(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)入同樣的路徑
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容