任務
使用者任務
描述
- 使用者任務用來設定必須由人員完成的工作
- 當流程執行到使用者任務,會建立一個新任務,并把這個新任務加入到配置設定人或群組的任務清單中
圖形标記
- 使用者任務顯示成一個普通任務(圓角矩形),左上角有一個小使用者圖示
XML内容
- XML中的使用者任務定義:id屬性是必須的,name屬性是可選的:
<userTask id="theTask" name="Important task" />
- 使用者任務可以設定描述,添加documentation元素可以定義描述:
<userTask id="theTask" name="Schedule meeting" >
<documentation>
Schedule an engineering meeting for next week with the new hire.
</documentation>
- 描述文本可以通過标準的java方法來擷取:
task.getDescription()
持續時間
- 任務可以用一個字段來描述任務的持續時間
- 可以使用查詢API來對持續時間進行搜尋,根據在時間之前或之後進行搜尋
- Activiti提供了一個節點擴充,在任務定義中設定一個表達式,這樣在任務建立時就可以設定初始持續時間
- 表達式應該是:
- java.util.Date
- java.util.String(ISO8601格式),ISO8601持續時間(比如PT50M)
- null
- 在流程中使用上述格式輸入日期,或在前一個服務任務中計算一個時間.這裡使用了持續時間,持續時間會基于目前時間進行計算,再通過給定的時間段累加: 使用"PT30M"作為持續時間,任務就會從現在開始持續30分鐘
<userTask id="theTask" name="Important task" activiti:dueDate="${dateVariable}"/>
- 任務的持續時間也可以通過TaskService修改,或在TaskListener中通過傳入的DelegateTask參數修改
使用者配置設定
- 使用者任務可以直接配置設定給一個使用者,通過humanPerformer元素定義
- humanPerformer定義需要一個resourceAssignmentExpression來實際定義使用者.目前隻支援formalExpressions
<process ... >
...
<userTask id='theTask' name='important task' >
<humanPerformer>
<resourceAssignmentExpression>
<formalExpression>kermit</formalExpression>
</resourceAssignmentExpression>
</humanPerformer>
</userTask>
- 隻有一個使用者可以作為任務的執行者配置設定使用者
- 在activiti中,使用者叫做執行者
- 擁有執行者的使用者不會出現在其他人的任務清單中,隻能出現執行者的個人任務清單中
- 直接配置設定給使用者的任務可以通過TaskService擷取:
List<Task> tasks = taskService.createTaskQuery().taskAssignee("kermit").list();
- 任務也可以加入到人員的候選任務清單中.需要使用potentialOwner元素
- 用法和humanPerformer元素類似,需要指定表達式中的每個項目是人員還是群組
<process ... >
...
<userTask id='theTask' name='important task' >
<potentialOwner>
<resourceAssignmentExpression>
<formalExpression>user(kermit), group(management)</formalExpression>
</resourceAssignmentExpression>
</potentialOwner>
</userTask>
- 使用potentialOwner元素定義的任務可以通過TaskService擷取:
List<Task> tasks = taskService.createTaskQuery().taskCandidateUser("kermit");
這會擷取所有kermit為候選人的任務,表達式中包含user(kermit).這也會獲得所有配置設定包含kermit這個成員的群組(比如,group(management),前提是kermit是這個組的成員,并且使用了activiti的賬号元件).使用者所在的群組是在運作階段擷取的, 它們可以通過IdentityService進行管理
- 如果沒有顯式指定設定的是使用者還是群組,引擎會預設當做群組處理
- 下面的設定與使用group(accountancy)一樣:
<formalExpression>accountancy</formalExpression>
Activiti對任務配置設定的擴充
- 當配置設定不複雜時,使用者群組的設定非常麻煩.為避免複雜性,可以使用使用者任務的自定義擴充
- assignee屬性: 直接把使用者任務配置設定給指定使用者(和使用humanPerformer 效果完全一樣)
<userTask id="theTask" name="my task" activiti:assignee="kermit" />
- candidateUsers屬性: 為任務設定候選人(和使用potentialOwner效果完全一樣,不需要像使用potentialOwner通過user(kermit)聲明,這個屬性隻能用于人員)
<userTask id="theTask" name="my task" activiti:candidateUsers="kermit, gonzo" />
- candidateGroups屬性: 為任務設定候選組(和使用potentialOwner效果完全一樣,不需要像使用potentialOwner通過group(management)聲明,這個屬性隻能用于群組)
<userTask id="theTask" name="my task" activiti:candidateGroups="management, accountancy" />
- candidateUsers和candidateGroups可以同時設定在同一個使用者任務中
- Activiti中雖然有賬号管理元件和IdentityService ,賬号元件不會檢測設定的使用者是否存在. Activiti允許與其他已存的賬戶管理方案內建
- 使用建立事件的任務監聽器 來實作自定義的配置設定邏輯:
<userTask id="task1" name="My task" >
<extensionElements>
<activiti:taskListener event="create" class="org.activiti.MyAssignmentHandler" />
</extensionElements>
</userTask>
- DelegateTask會傳遞給TaskListener的實作,通過它可以設定執行人,候選人和候選組
public class MyAssignmentHandler implements TaskListener {
public void notify(DelegateTask delegateTask) {
// Execute custom identity lookups here
// and then for example call following methods:
delegateTask.setAssignee("kermit");
delegateTask.addCandidateUser("fozzie");
delegateTask.addCandidateGroup("management");
...
}
}
- 使用spring時,使用表達式把任務監聽器設定為spring代理的bean,讓這個監聽器監聽任務的建立事件
- 示例:執行者會通過調用ldapService這個spring bean的findManagerOfEmployee方法獲得.流程變量emp會作為參數傳遞給bean
<userTask id="task" name="My Task" activiti:assignee="${ldapService.findManagerForEmployee(emp)}"/>
- 可以用來設定候選人和候選組:
<userTask id="task" name="My Task" activiti:candidateUsers="${ldapService.findAllSales()}"/>
- 方法傳回類型隻能為String(候選人) 或Collection < String >(候選組):
public class FakeLdapService {
public String findManagerForEmployee(String employee) {
return "Kermit The Frog";
}
public List<String> findAllSales() {
return Arrays.asList("kermit", "gonzo", "fozzie");
}
}
腳本任務
- 腳本任務是一個自動節點
- 當流程到達腳本任務,會執行對應的腳本
- 腳本任務顯示為标準BPMN 2.0任務(圓角矩形),左上角有一個腳本小圖示
- 腳本任務定義需要指定script和scriptFormat
<scriptTask id="theScriptTask" name="Execute script" scriptFormat="groovy">
<script>
sum = 0
for ( i in inputArray ) {
sum += i
}
</script>
</scriptTask>
scriptFormat的值必須相容JSR-223(java平台的腳本語言).預設Javascript會包含在JDK中,不需要額外的依賴.如果要使用其他的腳本引擎,必須要是JSR-223引擎相容的.還需要把對應的jar添加到classpath下, 并使用合适的名稱:activiti單元測試經常使用groovy
- groovy腳本引擎放在groovy-all.jar中,在2.0版本之前,腳本引擎是groovy jar的一部分.使用需要添加依賴:
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
<version>2.x.x<version>
</dependency>
腳本變量
- 到達腳本任務的流程可以通路的所有流程變量,都可以在腳本中使用
<script>
sum = 0
for ( i in inputArray ) {
sum += i
}
</script>
- 也可以在腳本中設定流程變量,直接調用execution.setVariable("variableName", variableValue)
- 預設,不會自動儲存變量(activiti 5.12之前)
- 可以在腳本中自動儲存任何變量,隻要把scriptTask的autoStoreVariables屬性設定為true
- 最佳實踐是不要使用,而是顯式調用execution.setVariable()
<scriptTask id="script" scriptFormat="JavaScript" activiti:autoStoreVariables="false">
參數預設為false: 如果沒有為腳本任務定義設定參數,所有聲明的變量将隻存在于腳本執行的階段
- 在腳本中設定變量: 這些命名已經被占用,不能用作變量名- out, out:print, lang:import, context, elcontext.
<script>
def scriptVar = "test123"
execution.setVariable("myVar", scriptVar)
</script>
腳本結果
- 腳本任務的傳回值可以通過制定流程變量的名稱,配置設定給已存在或者一個新流程變量,需要使用腳本任務定義的'activiti:resultVariable'屬性
- 任何已存在的流程變量都會被腳本執行的結果覆寫
- 如果沒有指定傳回的變量名,腳本的傳回值會被忽略
<scriptTask id="theScriptTask" name="Execute script" scriptFormat="juel" activiti:resultVariable="myVar">
<script>#{echo}</script>
</scriptTask>
腳本的結果-表達式 #{echo} 的值會在腳本完成後,設定到myVar變量中
Java服務任務
- Java服務任務用來調用外部Java類
- Java服務任務顯示為圓角矩形,左上角有一個齒輪小圖示
- 聲明Java調用邏輯有四種方式:
- 實作JavaDelegate或者ActivityBehavior
- 執行解析代理對象的表達式
- 調用一個方法表達式
- 調用一個值表達式
- 執行一個在流程執行中調用的類,需要在activiti:class屬性中設定全類名:
<serviceTask id="javaService"
name="My Java Service Task"
activiti:class="org.activiti.MyJavaDelegate" />
- 使用表達式調用一個對象,對象必須遵循一些規則,并使用activiti:delegateExpression屬性進行建立:
<serviceTask id="serviceTask" activiti:delegateExpression="${delegateExpressionBean}" />
delegateExpressionBean是一個實作了JavaDelegate接口的bean,定義在執行個體的spring容器中
要執行指定的UEL方法表達式, 需要使用activiti:expression:
<serviceTask id="javaService"
name="My Java Service Task"
activiti:expression="#{printer.printMessage()}" />
方法printMessage()會調用名為printer對象的方法
- 為表達式中的方法傳遞參數:
<serviceTask id="javaService"
name="My Java Service Task"
activiti:expression="#{printer.printMessage(execution, myVar)}" />
調用名為printer對象上的方法printMessage.第一個參數是DelegateExecution, 在表達式環境中預設名稱為execution. 第二個參數傳遞的是目前流程的名為myVar的變量
<serviceTask id="javaService"
name="My Java Service Task"
activiti:expression="#{split.ready}" />
ready屬性的getter方法:getReady() 會作用于名為split的bean上.這個對象會被解析為流程對象和spring環境中的對象
實作
- 要在流程執行中實作一個調用的類,這個類需要實作org.activiti.engine.delegate.JavaDelegate接口,并在execute方法中提供對應的業務邏輯.當流程執行到特定階段,會指定方法中定義好的業務邏輯,并按照預設BPMN 2.0中的方式離開節點
- 示例:
- 建立一個java類的例子,對流程變量中字元串轉換為大寫
- 這個類需要實作org.activiti.engine.delegate.JavaDelegate接口,要求實作execute(DelegateExecution) 方法,包含的業務邏輯會被引擎調用
- 流程執行個體資訊:流程變量和其他資訊,可以通過DelegateExecution接口通路和操作
public class ToUppercase implements JavaDelegate {
public void execute(DelegateExecution execution) throws Exception {
String var = (String) execution.getVariable("input");
var = var.toUpperCase();
execution.setVariable("input", var);
}
}
- serviceTask定義的class隻會建立一個java類的執行個體
- 所有流程執行個體都會共享相同的類執行個體,并調用execute(DelegateExecution)
- 類不能使用任何成員變量,必須是線程安全的,必須能模拟在不同線程中執行.影響着屬性注入的處理方式
- 流程定義中引用的類(activiti:class)不會在部署時執行個體化
- 隻有當流程第一次執行到使用類的時候,類的執行個體才會被建立
- 如果找不到類,會抛出一個ActivitiException
- 這個原因是部署環境(更确切是的classpath)和真實環境往往是不同的:當使用ant或業務歸檔上傳到Activiti Explorer來釋出流程,classpath沒有包含引用的類
- 内部實作類也可以提供實作org.activiti.engine.impl.pvm.delegate.ActivityBehavior接口的類
- 實作可以通路更強大的ActivityExecution,它可以影響流程的流向
- 注意: 這應該盡量避免.隻有在進階情況下并且确切知道要做什麼的情況下,再使用ActivityBehavior接口
屬性注入
- 為代理類的屬性注入資料. 支援如下類型的注入:
- 固定的字元串
- 表達式
- 如果有效的話,數值會通過代理類的setter方法注入,遵循java bean的命名規範(比如fistName屬性對應setFirstName(Xxx)方法)
- 如果屬性沒有對應的setter方法,數值會直接注入到私有屬性中
- 一些環境的SecurityManager不允許修改私有屬性,要把想注入的屬性暴露出對應的setter方法來
- 無論流程定義中的資料是什麼類型,注入目标的屬性類型都應該是 org.activiti.engine.delegate.Expression
-
- 把一個常量注入到屬性中
- 屬性注入可以使用class屬性
- 在聲明實際的屬性注入之前,需要定義一個extensionElements的XML元素
<serviceTask id="javaService"
name="Java service invocation"
activiti:class="org.activiti.examples.bpmn.servicetask.ToUpperCaseFieldInjected">
<extensionElements>
<activiti:field name="text" stringValue="Hello World" />
</extensionElements>
</serviceTask>
ToUpperCaseFieldInjected類有一個text屬性,類型是org.activiti.engine.delegate.Expression. 調用text.getValue(execution) 時,會傳回定義的字元串Hello World
- 可以使用長文字(比如,内嵌的email),使用activiti:string子元素:
<serviceTask id="javaService"
name="Java service invocation"
activiti:class="org.activiti.examples.bpmn.servicetask.ToUpperCaseFieldInjected">
<extensionElements>
<activiti:field name="text">
<activiti:string>
Hello World
</activiti:string>
</activiti:field>
</extensionElements>
</serviceTask>
- 可以使用表達式,實作在運作期動态解析注入的值
- 這些表達式可以使用流程變量或spring定義的bean.
- 服務任務中的java類執行個體會在所有流程執行個體中共享:
- 為了動态注入屬性的值,可以在org.activiti.engine.delegate.Expression中使用值和方法表達式
- 會使用傳遞給execute方法的DelegateExecution參數進行解析
<serviceTask id="javaService" name="Java service invocation"
activiti:class="org.activiti.examples.bpmn.servicetask.ReverseStringsFieldInjected">
<extensionElements>
<activiti:field name="text1">
<activiti:expression>${genderBean.getGenderString(gender)}</activiti:expression>
</activiti:field>
<activiti:field name="text2">
<activiti:expression>Hello ${gender == 'male' ? 'Mr.' : 'Mrs.'} ${name}</activiti:expression>
</activiti:field>
</ extensionElements>
</ serviceTask>
-
- 注入表達式,并使用在目前傳入的DelegateExecution解析:
public class ReverseStringsFieldInjected implements JavaDelegate {
private Expression text1;
private Expression text2;
public void execute(DelegateExecution execution) {
String value1 = (String) text1.getValue(execution);
execution.setVariable("var1", new StringBuffer(value1).reverse().toString());
String value2 = (String) text2.getValue(execution);
execution.setVariable("var2", new StringBuffer(value2).reverse().toString());
}
}
- 可以把表達式設定成一個屬性,而不是子元素:
- 因為java類執行個體會被重用,注入隻會發生一次,當服務任務調用第一次的時候發生注入
- 當代碼中的屬性改變了,值也不會重新注入,把它們看作是不變的,不用修改它們
服務任務結果
- 服務流程傳回的結果(使用表達式的服務任務)可以配置設定給已經存在的或新的流程變量
- 通過指定服務任務定義的activiti:resultVariable屬性來實作
- 指定的流程變量會被服務流程的傳回結果覆寫
- 如果沒有指定傳回變量名,就會忽略傳回結果
<serviceTask id="aMethodExpressionServiceTask"
activiti:expression="#{myService.doSomething()}"
activiti:resultVariable="myVar" />
服務流程的傳回值(在myService上調用doSomething() 方法的傳回值,myService可能是流程變量,也可能是spring的bean),在服務執行完成之後,會設定到名為myVar的流程變量裡
處理異常
執行自定義邏輯時,常常需要捕獲對應的業務異常,在流程内部進行處理
- 抛出BPMN Errors:
- 在服務任務或腳本任務的代碼裡抛出BPMN error:
- 要從JavaDelegate,腳本,表達式和代理表達式中抛出名為BpmnError的特殊ActivitiExeption
- 引擎會捕獲這個異常,把它轉發到對應的錯誤進行中:邊界錯誤事件或錯誤事件子流程
- 在服務任務或腳本任務的代碼裡抛出BPMN error:
public class ThrowBpmnErrorDelegate implements JavaDelegate {
public void execute(DelegateExecution execution) throws Exception {
try {
executeBusinessLogic();
} catch (BusinessException e) {
throw new BpmnError("BusinessExceptionOccured");
}
}
}
構造參數是錯誤代碼,會被用來決定哪個錯誤處理器會來響應這個錯誤
這個機制隻用于業務失敗,應該被流程定義中設定的邊界錯誤事件或錯誤事件子流程處理. 技術上的錯誤應該使用其他異常類型,通常不會在流程裡處理
- 異常順序流:
内部實作類在一些異常發生時,讓流程進入其他路徑
<serviceTask id="javaService"
name="Java service invocation"
activiti:class="org.activiti.ThrowsExceptionBehavior">
</serviceTask>
<sequenceFlow id="no-exception" sourceRef="javaService" targetRef="theEnd" />
<sequenceFlow id="exception" sourceRef="javaService" targetRef="fixException" />
這裡的服務任務有兩個外出順序流:分别叫exception和no-exception. 異常出現時會使用順序流的ID來決定流向
public class ThrowsExceptionBehavior implements ActivityBehavior {
public void execute(ActivityExecution execution) throws Exception {
String var = (String) execution.getVariable("var");
PvmTransition transition = null;
try {
executeLogic(var);
transition = execution.getActivity().findOutgoingTransition("no-exception");
} catch (Exception e) {
transition = execution.getActivity().findOutgoingTransition("exception");
}
execution.take(transition);
}
}
JavaDelegate使用Activiti服務
- 需要在Java服務任務中使用Activiti服務的場景: 比如,通過RuntimeService啟動流程執行個體,而callActivity不滿足需求
- org.activiti.engine.delegate.DelegateExecution允許通過 org.activiti.engine.EngineServices接口直接獲得這些服務:
public class StartProcessInstanceTestDelegate implements JavaDelegate {
public void execute(DelegateExecution execution) throws Exception {
RuntimeService runtimeService = execution.getEngineServices().getRuntimeService();
runtimeService.startProcessInstanceByKey("myProcess");
}
}
- 所有activiti服務的API都可以通過這個接口獲得
- 使用這些API調用出現的所有資料改變,都是在目前事務中
- 在例如spring和CDI這樣的依賴注入環境也會起作用,無論是否啟用了JTA資料源
- 示例: 下面的代碼功能與上面的代碼一緻,這是RuntimeService是通過依賴注入獲得,而不是通過org.activiti.engine.EngineServices接口
@Component("startProcessInstanceDelegate")
public class StartProcessInstanceTestDelegateWithInjection {
@Autowired
private RuntimeService runtimeService;
public void startProcess() {
runtimeService.startProcessInstanceByKey("oneTaskProcess");
}
}
- 因為服務調用是在目前事務裡,資料的産生或改變,在服務任務執行完之前,還沒有送出到資料庫.是以API對于資料庫資料的操作,意味着未送出的操作在服務任務的API調用中都是不可見的
WebService任務
- WebService任務可以用來同步調用一個外部的WebService
- WebService任務與Java服務任務顯示效果一樣(圓角矩形,左上角有一個齒輪小圖示)
- 要使用WebService需要導入操作和類型,可以使用import标簽來指定WebService的WSDL
<import importType="http://schemas.xmlsoap.org/wsdl/"
location="http://localhost:63081/counter?wsdl"
namespace="http://webservice.activiti.org/" />
聲明告訴activiti導入WSDL定義,但沒有建立itemDefinition和message
- 假設想調用一個名為prettyPrint的方法,必須建立為請求和響應資訊對應的message和itemDefinition
<message id="prettyPrintCountRequestMessage" itemRef="tns:prettyPrintCountRequestItem" />
<message id="prettyPrintCountResponseMessage" itemRef="tns:prettyPrintCountResponseItem" />
<itemDefinition id="prettyPrintCountRequestItem" structureRef="counter:prettyPrintCount" />
<itemDefinition id="prettyPrintCountResponseItem" structureRef="counter:prettyPrintCountResponse" />
- 在申請服務任務之前,必須定義實際引用WebService的BPMN接口和操作
- 基本上,定義接口和必要的操作.對每個操作都會重用上面定義的資訊作為輸入和輸出
-
- 定義了counter接口和prettyPrintCountOperation操作:
<interface name="Counter Interface" implementationRef="counter:Counter">
<operation id="prettyPrintCountOperation" name="prettyPrintCount Operation"
implementationRef="counter:prettyPrintCount">
<inMessageRef>tns:prettyPrintCountRequestMessage</inMessageRef>
<outMessageRef>tns:prettyPrintCountResponseMessage</outMessageRef>
</operation>
</interface>
然後定義WebService任務,使用WebService實作,并引用WebService操作
<serviceTask id="webService"
name="Web service invocation"
implementation="##WebService"
operationRef="tns:prettyPrintCountOperation">
WebService任務IO規範
- 每個WebService任務可以定義任務的輸入輸出IO規範
<ioSpecification>
<dataInput itemSubjectRef="tns:prettyPrintCountRequestItem" id="dataInputOfServiceTask" />
<dataOutput itemSubjectRef="tns:prettyPrintCountResponseItem" id="dataOutputOfServiceTask" />
<inputSet>
<dataInputRefs>dataInputOfServiceTask</dataInputRefs>
</inputSet>
<outputSet>
<dataOutputRefs>dataOutputOfServiceTask</dataOutputRefs>
</outputSet>
</ioSpecification>
WebService任務資料輸入關聯
- 指定資料輸入關聯有兩種方式:
- 使用表達式
- 使用簡化方式
- 使用表達式指定資料輸入關聯: 需要定義來源和目的item,并指定每個item屬性之間的對應關系:
<dataInputAssociation>
<sourceRef>dataInputOfProcess</sourceRef>
<targetRef>dataInputOfServiceTask</targetRef>
<assignment>
<from>${dataInputOfProcess.prefix}</from>
<to>${dataInputOfServiceTask.prefix}</to>
</assignment>
<assignment>
<from>${dataInputOfProcess.suffix}</from>
<to>${dataInputOfServiceTask.suffix}</to>
</assignment>
</dataInputAssociation>
配置設定item的字首和字尾
- 使用簡化方式指定資料輸入關聯: sourceRef元素是activiti的變量名,targetRef元素是item定義的一個屬性:
<dataInputAssociation>
<sourceRef>PrefixVariable</sourceRef>
<targetRef>prefix</targetRef>
</dataInputAssociation>
<dataInputAssociation>
<sourceRef>SuffixVariable</sourceRef>
<targetRef>suffix</targetRef>
</dataInputAssociation>
PrefixVariable變量的值配置設定給prefix屬性,把SuffixVariable變量的值配置設定給suffix屬性
WebService任務資料輸出關聯
- 指定資料輸出關聯有兩種方式:
- 使用表達式指定資料輸出關聯: 需要定義目的變量和來源表達式
<dataOutputAssociation>
<targetRef>dataOutputOfProcess</targetRef>
<transformation>${dataOutputOfServiceTask.prettyPrint}</transformation>
</dataOutputAssociation>
方法和資料輸入關聯完全一樣
- 使用簡化方式指定資料輸出關聯: sourceRef元素是item定義的一個屬性,targetRef元素是activiti的變量名
<dataOutputAssociation>
<sourceRef>prettyPrint</sourceRef>
<targetRef>OutputVariable</targetRef>
</dataOutputAssociation>
業務規則任務
- 業務規則任務用來同步執行一個或多個規則
- Activiti使用drools規則引擎執行業務規則:
- 包含業務規則的.drl檔案必須和流程定義一起釋出
- 流程定義裡包含了執行這些規則的業務規則任務
- 流程使用的所有.drl檔案都必須打包在流程BAR檔案裡
- 如果想要自定義規則任務的實作: 想用不同方式使用drools,或者使用完全不同的規則引擎.你可以使用BusinessRuleTask上的class或表達式屬性
- 業務規則任務是一個圓角矩形,左上角使用一個表格小圖示進行顯示
- 要執行部署流程定義的BAR檔案中的一個或多個業務規則,需要定義輸入和輸出變量:
- 對于輸入變量定義,可以使用逗号分隔的一些流程變量
- 輸出變量定義隻包含一個變量名,會把執行業務規則後傳回的對象儲存到對應的流程變量中
- 注意: 結果變量會包含一個對象清單,如果沒有指定輸出變量名稱,預設會使用 org.activiti.engine.rules.OUTPUT
<process id="simpleBusinessRuleProcess">
<startEvent id="theStart" />
<sequenceFlow sourceRef="theStart" targetRef="businessRuleTask" />
<businessRuleTask id="businessRuleTask" activiti:ruleVariablesInput="${order}"
activiti:resultVariable="rulesOutput" />
<sequenceFlow sourceRef="businessRuleTask" targetRef="theEnd" />
<endEvent id="theEnd" />
</process>
- 業務規則任務也可以配置成隻執行部署的.drl檔案中的一些規則.這時要設定逗号分隔的規則名,隻會執行rule1和rule2:
<businessRuleTask id="businessRuleTask" activiti:ruleVariablesInput="${order}"
activiti:rules="rule1, rule2" />
- 定義哪些規則不用執行:除了rule1和rule2以外,所有部署到流程定義同一個BAR檔案中的規則都會執行:
<businessRuleTask id="businessRuleTask" activiti:ruleVariablesInput="${order}"
activiti:rules="rule1, rule2" exclude="true" />
- 可以用一個選項修改BusinessRuleTask的實作:
<businessRuleTask id="businessRuleTask" activiti:class="${MyRuleServiceDelegate}" />
BusinessRuleTask的功能和ServiceTask一樣,但是使用BusinessRuleTask的圖示來表示 在這裡要執行業務規則
郵件任務
- Activiti強化了業務流程,支援自動郵件任務:
- 可以發送郵件給一個或多個參與者,包括支援cc,bcc,HTML内容等等
- 郵件任務不是BPMN 2.0規範定義的官方任務,Activiti中郵件任務是用專門的服務任務實作的
郵件伺服器配置
- Activiti引擎要通過支援SMTP功能的外部郵件伺服器發送郵件
- 為了實際發送郵件,引擎穾知道如何通路郵件伺服器.下面的配置可以設定到activiti.cfg.xml配置檔案中:
屬性 | 是否必須 | |
---|---|---|
mailServerHost | 否 | 郵件伺服器的主機名(比如:mail.mycorp.com).預設為localhost |
mailServerPort | 是 如果沒有使用預設端口 | 郵件伺服器上的SMTP傳輸端口.預設為25 |
mailServerDefaultFrom | 如果使用者沒有指定發送郵件的郵件位址,預設設定的發送者的郵件位址。預設為[email protected] | |
mailServerUsername | 如果伺服器需要 | 一些郵件伺服器需要認證才能發送郵件.預設不設定 |
mailServerPassword | ||
mailServerUseSSL | 一些郵件伺服器需要ssl互動.預設為false | |
mailServerUseTLS | 一些郵件伺服器(比如gmail)需要支援TLS.預設為false |
定義一個郵件任務
- 郵件任務是一個專用的服務任務, 這個服務任務的type設定為mail
<serviceTask id="sendMail" activiti:type="mail">
- 郵件任務是通過屬性注入進行配置的.所有這些屬性都可以使用EL表達式,可以在流程執行中解析. 下面的屬性都可以設定:
to | 郵件的接受者.可以使用逗号分隔多個接受者 | |
from | 郵件發送者的位址.如果不提供,會使用預設配置的位址 | |
subject | 郵件的主題 | |
cc | 郵件抄送人.可以使用逗号分隔多個接收者 | |
bcc | 郵件暗送人.可以使用逗号分隔多個接收者 | |
charset | 可以修改郵件的字元集,對很多非英語語言是必須設定的 | |
html | 作為郵件内容的HTML | |
text | 郵件的内容.,在需要使用原始文字(非富文本)的郵件時使用.可以與html一起使用,對于不支援富文本的郵件用戶端.用戶端會降級到僅顯示文本的方式 | |
htmlVar | 使用對應的流程變量作為e-mail的内容.和html的不同之處是内容中包含的表達式會在mail任務發送之前被替換掉 | |
textVar | 使用對應的流程變量作為e-mail的純文字内容.和text的不同之處是内容中包含的表達式會在mail任務發送之前被替換掉 | |
ignoreException | 處理郵件失敗時,是否忽略異常,不抛出ActivitiException,預設為false | |
exceptionVariableName | 當設定了ignoreException=true處理email時不抛出異常,可以指定一個變量名來存儲失敗資訊 |
執行個體
- 郵件任務的使用示例:
<serviceTask id="sendMail" activiti:type="mail">
<extensionElements>
<activiti:field name="from" stringValue="[email protected]" />
<activiti:field name="to" expression="${recipient}" />
<activiti:field name="subject" expression="Your order ${orderId} has been shipped" />
<activiti:field name="html">
<activiti:expression>
<![CDATA[
<html>
<body>
Hello ${male ? 'Mr.' : 'Mrs.' } ${recipientName},<br/><br/>
As of ${now}, your order has been <b>processed and shipped</b>.<br/><br/>
Kind regards,<br/>
TheCompany.
</body>
</html>
]]>
</activiti:expression>
</activiti:field>
</extensionElements>
</serviceTask>
Mule任務
- Mule任務可以向Mule發送消息,用來強化Activiti的內建能力
- Mule任務不是BPMN 2.0規範定義的官方任務,Activiti中Mule任務是用專門的服務任務實作的
定義Mule任務
- Mule任務是一個專用的服務任務, 服務任務的type設定為mule
<serviceTask id="sendMule" activiti:type="mule">
- Mule任務是通過屬性注入進行配置的.屬性使用EL表達式, 可以在流程執行中解析
endpointUrl | 需要調用的Mule終端 | |
language | 要使用解析荷載表達式(payloadExpression)屬性的語言 | |
payloadExpression | 作為消息荷載的表達式 | |
resultVariable | 将要儲存調用結果的變量名稱 |
- Mule任務的使用示例:
<extensionElements>
<activiti:field name="endpointUrl">
<activiti:string>vm://in</activiti:string>
</activiti:field>
<activiti:field name="language">
<activiti:string>juel</activiti:string>
</activiti:field>
<activiti:field name="payloadExpression">
<activiti:string>"hi"</activiti:string>
</activiti:field>
<activiti:field name="resultVariable">
<activiti:string>theVariable</activiti:string>
</activiti:field>
</extensionElements>
Camel任務
- Camel任務可以從Camel發送和接收消息,用來強化activiti的內建功能
- Camel任務不是BPMN 2.0規範定義的官方任務,Camel任務時由專用的服務任務實作的
- 使用Camel任務功能,要把Activiti Camel包含到項目中
定義Camel任務
- Camel任務是一個專用的服務任務, 服務任務的type設定為camel
<serviceTask id="sendCamel" activiti:type="camel">
- 流程定義隻需要在服務任務中定義Camel類型
- 內建邏輯都會代理給Camel容器
- 預設Activiti引擎會在spring容器中查找camelContext bean.camelContext定義了camel容器加載的路由規則
- 路由規則是既可以從指定的java包下加載, 也可以通過spring配置直接定義路由規則
<camelContext id="camelContext" xmlns="http://camel.apache.org/schema/spring">
<packageScan>
<package>org.activiti.camel.route</package>
</packageScan>
</camelContext>
- 定義多個Camel環境bean,并且使用不同的bean名稱. 可以重載CamelTask的定義
<serviceTask id="serviceTask1" activiti:type="camel">
<extensionElements>
<activiti:field name="camelContext" stringValue="customCamelContext" />
</extensionElements>
</serviceTask>
Camel調用
- 為了激活一個特定的Camel路由:
- 需要一個Spring環境,包含SimpleCamelCallRoute的路由的類檔案,放在packageScan标簽的掃描目錄下
<camelContext id="camelContext" xmlns="http://camel.apache.org/schema/spring">
<packageScan>
<package>org.activiti.camel.examples.simpleCamelCall</package>
</packageScan>
</camelContext>
- 路由的定義:
public class SimpleCamelCallRoute extends RouteBuilder {
@Override
public void configure() throws Exception {
from("activiti:SimpleCamelCallProcess:simpleCall").to("log: org.activiti.camel.examples.SimpleCamelCall");
}
}
這個規則用于列印消息體
- 終端的格式包含三部分:
- 終端URL: 引用activiti終端
- SimpleCamelCallProcess: 流程名
- simpleCall: 流程中的Camel服務
- 配置好規則後,可以讓Camel進行使用.工作流如下:
<process id="SimpleCamelCallProcess">
<startEvent id="start"/>
<sequenceFlow id="flow1" sourceRef="start" targetRef="simpleCall"/>
<serviceTask id="simpleCall" activiti:type="camel"/>
<sequenceFlow id="flow2" sourceRef="simpleCall" targetRef="end"/>
<endEvent id="end"/>
</process>
在serviceTask部分,注明服務的類型是Camel, 目标規則名為simpleCall. 這與上面的Activiti終端相比對.初始化流程後,會看到一個空的日志
乒乓執行個體
- Camel和Activiti之間需要互動,向Camel發送和接收資料
- 發送一個字元串,把變量裡的消息發送給Camel,Camel進行一些處理,然後傳回結果:
@Deployment
public void testPingPong() {
Map<String, Object> variables = new HashMap<String, Object>();
variables.put("input", "Hello");
Map<String, String> outputMap = new HashMap<String, String>();
variables.put("outputMap", outputMap);
runtimeService.startProcessInstanceByKey("PingPongProcess", variables);
assertEquals(1, outputMap.size());
assertNotNull(outputMap.get("outputValue"));
assertEquals("Hello World", outputMap.get("outputValue"));
}
- 變量input是Camel規則的實際輸入 ,outputMap會記錄camel傳回的結果
<process id="PingPongProcess">
<startEvent id="start"/>
<sequenceFlow id="flow1" sourceRef="start" targetRef="ping"/>
<serviceTask id="ping" activiti:type="camel"/>
<sequenceFlow id="flow2" sourceRef="ping" targetRef="saveOutput"/>
<serviceTask id="saveOutput" activiti:class="org.activiti.camel.examples.pingPong.SaveOutput" />
<sequenceFlow id="flow3" sourceRef="saveOutput" targetRef="end"/>
<endEvent id="end"/>
</process>
- SaveOuput這個serviceTask, 會把Output變量的值從上下文儲存到OutputMap中
- 變量送出給Camel的方法是由CamelBehavior控制的.配置一個期望的Camel操作:
<serviceTask id="serviceTask1" activiti:type="camel">
<extensionElements>
<activiti:field name="camelBehaviorClass" stringValue="org.activiti.camel.impl.CamelBehaviorCamelBodyImpl" />
</extensionElements>
</serviceTask>
- 如果特别指定一個行為,就會使用org.activiti.camel.impl.CamelBehaviorDefaultImpl. 這個行為會把變量複制成名稱相同的Camel屬性
- 在傳回時,無論選擇什麼行為,如果camel消息體是一個map,每個元素都會複制成一個變量.否則整個對象會複制到指定名稱為camelBody的變量中
@Override
public void configure() throws Exception {
from("activiti:PingPongProcess:ping").transform().simple("${property.input} World");
}
在這個規則中,字元串world會被添加到input屬性的後面,結果會寫入消息體
這時可以檢查javaServiceTask中的camelBody變量,複制到outputMap中,并在testcase進行判斷
- 在啟動的所有camel規則中 ,流程執行個體ID會複制到Camel的名為PROCESS_ID_PROPERTY的屬性中,後續可以用來關聯流程執行個體和Camel規則,也可以在camel規則中直接使用
- Activiti中可以使用三種不同Camel的行為: 可以通過在規則URL中指定來實作覆寫
from("activiti:asyncCamelProcess:serviceTaskAsync2?copyVariablesToProperties=true").
Activiti變量如何傳遞給camel:
行為 | URL | |
---|---|---|
CamelBehaviorDefaultImpl | copyVariablesToProperties | 把Activiti變量複制為Camel屬性 |
CamelBehaviorCamelBodyImpl | copyCamelBodyToBody | 隻把名為"camelBody"Activiti變量複制成Camel的消息體 |
CamelBehaviorBodyAsMapImpl | copyVariablesToBodyAsMap | 把Activiti的所有變量複制到一個map裡,作為Camel的消息體 |
Camel的變量如何傳回給Activiti,隻能配置在規則URL中:
預設 | 如果Camel消息體是一個map,把每個元素複制成Activiti的變量.否則把整個Camel消息體作為Activiti的camelBody變量 |
copyVariablesFromProperties | 把Camel屬性以相同名稱複制為Activiti變量 |
copyCamelBodyToBodyAsString | 和預設一樣,但是如果camel消息體不是map時,先把它轉換成字元串,再設定為camelBody |
copyVariablesFromHeader | 額外把Camel頭部以相同名稱複制成Activiti變量 |
異步乒乓執行個體
- 同步的乒乓執行個體,流程會等到Camel規則傳回之後才會停止
- 某些情況下,需要Activiti工作流繼續運作,就要使用camelServiceTask的異步功能
- 通過設定camelServiceTask的async屬性來啟用這個功能
<serviceTask id="serviceAsyncPing" activiti:type="camel" activiti:async="true"/>
Camel規則會被Activiti的jobExecutor異步執行
當在Camel規則中定義了一個隊列,Activiti流程會在camelServiceTask執行時繼續運作
camel規則以完全異步的方式執行
- 可以使用一個receiveTask等待camelServiceTask的傳回值,流程執行個體會等到接收一個來自camel的signal:
<receiveTask id="receiveAsyncPing" name="Wait State" />
- 在Camel中可以發送一個signal給流程執行個體,通過對應的Activiti終端發送消息:
from("activiti:asyncPingProcess:serviceAsyncPing").to("activiti:asyncPingProcess:receiveAsyncPing");
- 在Activiti終端中,會使用冒号分隔的三個部分:
- 常量字元串activiti
- 流程名稱
- 接收任務名
Camel規則中執行個體化工作流
- 一般情況下,Activiti工作流會先啟動,然後在流程中啟動Camel規則
- 在已經啟動的Camel規則中啟動一個工作流,會觸發一個receiveTask
- 十分類似,除了最後的部分.執行個體規則如下:
from("direct:start").to("activiti:camelProcess");
url有兩個部分:
- 流程的名稱
流程已經部署完成,并且是可以啟動的
手工任務
- 手工任務定義了BPMN引擎外部的任務
- 表示工作需要某人完成,而引擎不需要知道, 也沒有對應的系統和UI接口
- 對于BPMN引擎而言,手工任務是直接通過的活動,流程到達它之後會自動向下執行
- 手工任務顯示為普通任務(圓角矩形),左上角是一個手型小圖示
<manualTask id="myManualTask" name="Call client for more information" />
Java接收任務
- 接收任務是一個簡單任務,會等待對應消息的到達
- 當流程達到接收任務,流程狀态會儲存到存儲裡.意味着流程會等待在這個等待狀态,直到引擎接收了一個特定的消息,觸發流程穿過接收任務繼續執行
- 接收任務顯示為一個任務(圓角矩形),右上角有一個消息小标記.消息是白色的(黑色圖示表示發送語義)
<receiveTask id="waitState" name="wait" />
- 要在接收任務等待的流程執行個體繼續執行,可以調用runtimeService.signal(executionId), 傳遞接收任務上流程的id:
ProcessInstance pi = runtimeService.startProcessInstanceByKey("receiveTask");
Execution execution = runtimeService.createExecutionQuery()
.processInstanceId(pi.getId())
.activityId("waitState")
.singleResult();
assertNotNull(execution);
runtimeService.signal(execution.getId());
Shell任務
- Shell任務可以執行Shell腳本和指令
- Shell任務不是BPMN 2.0規範定義的官方任務,沒有對應的圖示
定義Shell任務
- Shell任務是一個專用的服務任務,這個服務任務的type設定為shell
<serviceTask id="shellEcho" activiti:type="shell">
- Shell任務使用屬性注入進行配置,所有屬性都可以包含EL表達式, 會在流程執行過程中解析
類型 | 預設值 | |||
---|---|---|---|---|
command | String | 執行的shell指令 | ||
arg0-5 | 參數0至5 | |||
wait | true/false | 是否需要等待到shell程序結束 | true | |
redirectError | 把标準錯誤列印到标準流中 | false | ||
cleanEnv | Shell進行不繼承目前環境 | |||
outputVariable | 儲存輸出的變量名 | 不會記錄輸出結果 | ||
errorCodeVariable | 包含結果錯誤代碼的變量名 | 不會注冊錯誤級别 | ||
directory | Shell程序的預設目錄 | 目前目錄 |
應用執行個體
- 執行shell腳本cmd /c echo EchoTest, 等到它結束,再把輸出結果儲存到resultVar中:
<serviceTask id="shellEcho" activiti:type="shell" >
<extensionElements>
<activiti:field name="command" stringValue="cmd" />
<activiti:field name="arg1" stringValue="/c" />
<activiti:field name="arg2" stringValue="echo" />
<activiti:field name="arg3" stringValue="EchoTest" />
<activiti:field name="wait" stringValue="true" />
<activiti:field name="outputVariable" stringValue="resultVar" />
</extensionElements>
</serviceTask>
執行監聽器
- 執行監聽器可以在流程定義中發生了某個事件時執行外部Java代碼或執行表達式
- 執行監聽器可以捕獲的事件有:
- 流程執行個體的啟動和結束
- 選中一條連線
- 節點的開始和結束
- 網關的開始和結束
- 中間事件的開始和結束
- 開始時間結束或結束事件開始
- 下面的流程定義定義了3個流程監聽器:
<process id="executionListenersProcess">
<extensionElements>
<activiti:executionListener class="org.activiti.examples.bpmn.executionlistener.ExampleExecutionListenerOne" event="start" />
</extensionElements>
<startEvent id="theStart" />
<sequenceFlow sourceRef="theStart" targetRef="firstTask" />
<userTask id="firstTask" />
<sequenceFlow sourceRef="firstTask" targetRef="secondTask">
<extensionElements>
<activiti:executionListener class="org.activiti.examples.bpmn.executionListener.ExampleExecutionListenerTwo" />
</extensionElements>
</sequenceFlow>
<userTask id="secondTask" >
<extensionElements>
<activiti:executionListener expression="${myPojo.myMethod(execution.event)}" event="end" />
</extensionElements>
</userTask>
<sequenceFlow sourceRef="secondTask" targetRef="thirdTask" />
<userTask id="thirdTask" />
<sequenceFlow sourceRef="thirdTask" targetRef="theEnd" />
<endEvent id="theEnd" />
</process>
- 第一個流程監聽器監聽流程開始. 監聽器是一個外部Java類(例如ExampleExecutionListenerOne),需要實作org.activiti.engine.delegate.ExecutionListener接口.當事件發生時(end事件),會調用notify(ExecutionListenerExecution execution) 方法:
public class ExampleExecutionListenerOne implements ExecutionListener {
public void notify(ExecutionListenerExecution execution) throws Exception {
execution.setVariable("variableSetInExecutionListener", "firstValue");
execution.setVariable("eventReceived", execution.getEventName());
}
}
也可以使用實作org.activiti.engine.delegate.JavaDelegate接口的代理類(代理類可以在結構中重用,比如serviceTask的代理)
- 第二個流程監聽器在連線執行時調用. 注意這個listener元素不能定義event, 因為連線隻能觸發take事件,為連線定義的監聽器的event屬性會被忽略
- 第三個流程監聽器在節點secondTask結束時調用. 使用expression代替class來在事件觸發時執行或調用
<activiti:executionListener expression="${myPojo.myMethod(execution.eventName)}" event="end" />
流程變量可以處理和使用
流程實作對象有一個儲存事件名稱的屬性,在方法中使用execution.eventName獲的事件名稱
- 流程監聽器也支援delegateExpression, 和服務任務相同
<activiti:executionListener event="start" delegateExpression="${myExecutionListenerBean}" />
- 腳本流程監聽器org.activiti.engine.impl.bpmn.listener.ScriptExecutionListener可以為某個流程監聽事件執行一段腳本
<activiti:executionListener event="start" class="org.activiti.engine.impl.bpmn.listener.ScriptExecutionListener" >
<activiti:field name="script">
<activiti:string>
def bar = "BAR"; // local variable
foo = "FOO"; // pushes variable to execution context
execution.setVariable("var1", "test"); // test access to execution instance
bar // implicit return value
</activiti:string>
</activiti:field>
<activiti:field name="language" stringValue="groovy" />
<activiti:field name="resultVariable" stringValue="myVar" />
<activiti:executionListener>
流程監聽器的屬性注入
- 流程監聽器時,可以配置class屬性,使用屬性注入.這和使用服務任務屬性注入相同
- 使用屬性注入的流程監聽器的流程示例:
<process id="executionListenersProcess">
<extensionElements>
<activiti:executionListener class="org.activiti.examples.bpmn.executionListener.ExampleFieldInjectedExecutionListener" event="start">
<activiti:field name="fixedValue" stringValue="Yes, I am " />
<activiti:field name="dynamicValue" expression="${myVar}" />
</activiti:executionListener>
</extensionElements>
<startEvent id="theStart" />
<sequenceFlow sourceRef="theStart" targetRef="firstTask" />
<userTask id="firstTask" />
<sequenceFlow sourceRef="firstTask" targetRef="theEnd" />
<endEvent id="theEnd" />
</process>
public class ExampleFieldInjectedExecutionListener implements ExecutionListener {
private Expression fixedValue;
private Expression dynamicValue;
public void notify(ExecutionListenerExecution execution) throws Exception {
execution.setVariable("var", fixedValue.getValue(execution).toString() + dynamicValue.getValue(execution).toString());
}
}
ExampleFieldInjectedExecutionListener類串聯了兩個注入的屬性(一個是固定的,一個是動态的),把他們儲存到流程變量var中
@Deployment(resources = {"org/activiti/examples/bpmn/executionListener/ExecutionListenersFieldInjectionProcess.bpmn20.xml"})
public void testExecutionListenerFieldInjection() {
Map<String, Object> variables = new HashMap<String, Object>();
variables.put("myVar", "listening!");
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("executionListenersProcess", variables);
Object varSetByListener = runtimeService.getVariable(processInstance.getId(), "var");
assertNotNull(varSetByListener);
assertTrue(varSetByListener instanceof String);
// Result is a concatenation of fixed injected field and injected expression
assertEquals("Yes, I am listening!", varSetByListener);
}
任務監聽器
- 任務監聽器可以在發生對應的任務相關事件時執行自定義Java邏輯或表達式
- 任務監聽器隻能添加到流程定義中的使用者任務中. 必須定義在BPMN 2.0 extensionElements的子元素中,并使用activiti命名空間, 因為任務監聽器是activiti獨有的結構
<userTask id="myTask" name="My Task" >
<extensionElements>
<activiti:taskListener event="create" class="org.activiti.MyTaskCreateListener" />
</extensionElements>
</userTask>
- 任務監聽器支援的屬性:
- event(必選): 任務監聽器會被調用的任務類型
- create: 任務建立并設定所有屬性後觸發
- assignment: 任務配置設定給一些人時觸發.當流程到達userTask,assignment事件會在create事件之前發生(當獲得create時間時,我們想獲得任務的所有屬性,包括執行人)
- complete: 當任務完成,并尚未從運作資料中删除時觸發
- delete: 隻在任務删除之前發生,在通過completeTask正常完成時,也會執行
- class: 必須調用的代理類, 類要實作org.activiti.engine.delegate.TaskListener接口
public class MyTaskCreateListener implements TaskListener { public void notify(DelegateTask delegateTask) { // Custom logic goes here } }
可以使用屬性注入把流程變量或執行傳遞給代理類
代理類的執行個體是在部署時建立的,所有流程執行個體都會共享同一個執行個體
- expression: 指定事件發生時執行的表達式.無法同時與class屬性一起使用. 可以把DelegateTask對象和事件名稱(task.eventName)作為參數傳遞給調用的對象
<activiti:taskListener event="create" expression="${myObject.callMethod(task, task.eventName)}" />
- delegateExpression: 指定一個表達式,解析一個實作了TaskListener接口的對象,與服務任務一緻
<activiti:taskListener event="create" delegateExpression="${myTaskListenerBean}" />
- delegateExpression: 指定一個表達式,解析一個實作了TaskListener接口的對象,與服務任務一緻
- event(必選): 任務監聽器會被調用的任務類型
- 腳本任務監聽器org.activiti.engine.impl.bpmn.listener.ScriptTaskListener可以為任務監聽器事件執行腳本
<activiti:taskListener event="complete" class="org.activiti.engine.impl.bpmn.listener.ScriptTaskListener" >
<activiti:field name="script">
<activiti:string>
def bar = "BAR"; // local variable
foo = "FOO"; // pushes variable to execution context
task.setOwner("kermit"); // test access to task instance
bar // implicit return value
</activiti:string>
</activiti:field>
<activiti:field name="language" stringValue="groovy" />
<activiti:field name="resultVariable" stringValue="myVar" />
<activiti:taskListener>
多執行個體(循環)
- 多執行個體節點是在業務流程中定義重複環節的方法
- 多執行個體和循環是一樣的:它可以根據給定的集合,為每個元素執行一個環節甚至一個完整的子流程,既可以順序依次執行也可以并發同步執行
- 多執行個體是在一個普通的節點上添加了額外的屬性定義(是以叫做'多執行個體特性),這樣運作時節點就會執行多次
- 可以設定成多執行個體節點的節點:
- User Task
- Script Task
- Java Service Task
- WebService Task
- Business Rule Task
- Email Task
- Manual Task
- Receive Task
- (Embedded) Sub-Process [(嵌入子)流程]
- Call Activity [調用子流程]
- 網關和事件不能設定多執行個體
- 每個上級流程為每個執行個體建立分支時都要提供下列變量:
- nrOfInstances: 執行個體總數
- nrOfActiveInstances: 目前活動,還沒完成的執行個體數量. 順序執行的多執行個體,值一直為1
- nrOfCompletedInstances: 已經完成執行個體的數目
- 通過execution.getVariable(Xx) 方法獲得這些變量
- 每個建立的分支都會有分支級别的本地變量(例如其他執行個體不可見,不會儲存到流程執行個體級别):
- loopCounter- 特定執行個體的在循環的索引值
- 使用activiti的elementIndexVariable屬性修改loopCounter的變量名
- 多執行個體的節點,會在節點底部顯示三條短線.三條豎線表示執行個體會并行執行. 三條橫線表示順序執行
- 要把一個節點設定為多執行個體,節點xml元素必須設定一個multiInstanceLoopCharacteristics子元素
<multiInstanceLoopCharacteristics isSequential="false|true">
...
</multiInstanceLoopCharacteristics>
isSequential屬性表示節點是進行順序執行還是并行執行
- 執行個體的數量會在進入節點時計算一次:
- 一種方法是使用loopCardinality子元素
可以使用loopCardinality子元素中直接指定一個數字
<multiInstanceLoopCharacteristics isSequential="false|true">
<loopCardinality>5</loopCardinality>
</multiInstanceLoopCharacteristics>
也可以使用**loopCardinality**子元素中結果為整數的表達式
```xml
<multiInstanceLoopCharacteristics isSequential="false|true">
<loopCardinality>${nrOfOrders-nrOfCancellations}</loopCardinality>
</multiInstanceLoopCharacteristics>
```
- 另一個方法是通過loopDataInputRef子元素,設定一個類型為集合的流程變量名.對于集合中的每個元素,都會建立一個執行個體.也可以通過inputDataItem子元素指定集合
假設assigneeList變量包含這些值[kermit, gonzo, foziee],三個使用者任務會同時建立.每個分支都會擁有一個用名為assignee的流程變量,這個變量會包含集合中的對應元素,上面是用來設定使用者任務的配置設定者<userTask id="miTasks" name="My Task ${loopCounter}" activiti:assignee="${assignee}"> <multiInstanceLoopCharacteristics isSequential="false"> <loopDataInputRef>assigneeList</loopDataInputRef> <inputDataItem name="assignee" /> </multiInstanceLoopCharacteristics> </userTask>
- loopDataInputRef和inputDataItem的缺點:
- 名字難于記憶
- 根據BPMN 2.0格式定義 ,不能包含表達式
- 在activiti中可以在multiInstanceCharacteristics中設定collection和elementVariable
<userTask id="miTasks" name="My Task" activiti:assignee="${assignee}">
<multiInstanceLoopCharacteristics isSequential="true"
activiti:collection="${myService.resolveUsersForTask()}" activiti:elementVariable="assignee" >
</multiInstanceLoopCharacteristics>
</userTask>
- 多執行個體節點在所有執行個體都完成時才會結束
- 可以指定一個表達式在每個執行個體結束時執行,如果表達式傳回true,所有其它的執行個體都會銷毀,多執行個體節點也會結束.流程會繼續執行. 表達式必須定義在completionCondition子元素中
<userTask id="miTasks" name="My Task" activiti:assignee="${assignee}">
<multiInstanceLoopCharacteristics isSequential="false"
activiti:collection="assigneeList" activiti:elementVariable="assignee" >
<completionCondition>${nrOfCompletedInstances/nrOfInstances >= 0.6 }</completionCondition>
</multiInstanceLoopCharacteristics>
</userTask>
assigneeList集合的每個元素都會建立一個并行的執行個體,當60%的任務完成時,其他任務就會删除,流程繼續執行
邊界事件和多執行個體
- 多執行個體是一個普通節點,可以在邊緣使用邊界事件
- 對于中斷型邊界事件,當捕獲事件時,所有激活的執行個體都會銷毀
子流程的所有執行個體都會在定時器觸發時銷毀,無論有多少執行個體,也不論内部節點沒有完成
補償處理器
- 如果一個節點用來補償另一個節點的業務, 可以聲明為一個補償處理器
- 補償處理器不包含普通的流,隻在補償事件觸發時執行
- 補償處理器不能包含進入和外出順序流
- 補償處理器必須使用直接關聯配置設定給一個補償邊界事件
- 節點是補償處理器,補償事件圖示會顯示在中間底部區域
- 補償處理器圖形示例:一個服務任務,附加了一個補償邊界事件,并配置設定了一個補償處理器.注意cancel hotel reservation服務任務中間底部區域顯示的補償處理器圖示
- 聲明作為補償處理器的節點,需要把isForCompensation設定為true:
<serviceTask id="undoBookHotel" isForCompensation="true" activiti:class="...">
</serviceTask>