程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> JAVA綜合教程 >> Activiti工作流學習-----基於5.19.0版本(7),activiti5.19.0

Activiti工作流學習-----基於5.19.0版本(7),activiti5.19.0

編輯:JAVA綜合教程

Activiti工作流學習-----基於5.19.0版本(7),activiti5.19.0


八、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啟動流程實例需要統一的處理方式的時候是有用處的。

 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved