任務
用戶任務
描述
- 用戶任務用來設置必須由人員完成的工作
- 當流程執行到用戶任務,會創建一個新任務,並把這個新任務加入到分配人或群組的任務列表中
圖形標記
- 用戶任務顯示成一個普通任務(圓角矩形),左上角有一個小用戶圖標
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任務(圓角矩形),左上角有一個腳本小圖標
XML內容
- 腳本任務定義需要指定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服務任務顯示為圓角矩形,左上角有一個齒輪小圖標
XML內容
- 聲明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的變量
要執行指定的UEL方法表達式, 需要使用activiti:expression:
<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服務任務顯示效果一樣(圓角矩形,左上角有一個齒輪小圖標)
XML內容
- 要使用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或表達式屬性
圖形標記
- 業務規則任務是一個圓角矩形,左上角使用一個表格小圖標進行顯示
XML內容
- 要執行部署流程定義的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 | 否 | 如果用戶沒有指定發送郵件的郵件地址,默認設置的發送者的郵件地址。默認為activiti@activiti.org |
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="order-shipping@thecompany.com" />
<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中: | ||
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有兩個部分:
- 常量字符串activiti
- 流程的名稱
流程已經部署完成,並且是可以啟動的
手工任務
描述
- 手工任務定義了BPMN引擎外部的任務
- 表示工作需要某人完成,而引擎不需要知道, 也沒有對應的系統和UI接口
- 對於BPMN引擎而言,手工任務是直接通過的活動,流程到達它之后會自動向下執行
圖形標記
- 手工任務顯示為普通任務(圓角矩形),左上角是一個手型小圖標
XML內容
<manualTask id="myManualTask" name="Call client for more information" />
Java接收任務
描述
- 接收任務是一個簡單任務,會等待對應消息的到達
- 當流程達到接收任務,流程狀態會保存到存儲里.意味着流程會等待在這個等待狀態,直到引擎接收了一個特定的消息,觸發流程穿過接收任務繼續執行
圖形標記
- 接收任務顯示為一個任務(圓角矩形),右上角有一個消息小標記.消息是白色的(黑色圖標表示發送語義)
XML內容
<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 | 否 | String | 參數0至5 | |
wait | 否 | true/false | 是否需要等待到shell進程結束 | true |
redirectError | 否 | true/false | 把標准錯誤打印到標准流中 | false |
cleanEnv | 否 | true/false | Shell進行不繼承當前環境 | false |
outputVariable | 否 | String | 保存輸出的變量名 | 不會記錄輸出結果 |
errorCodeVariable | 否 | String | 包含結果錯誤代碼的變量名 | 不會注冊錯誤級別 |
directory | 否 | String | 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}" />
- 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內容
- 要把一個節點設置為多實例,節點xml元素必須設置一個multiInstanceLoopCharacteristics子元素
<multiInstanceLoopCharacteristics isSequential="false|true">
...
</multiInstanceLoopCharacteristics>
isSequential屬性表示節點是進行順序執行還是並行執行
- 實例的數量會在進入節點時計算一次:
- 一種方法是使用loopCardinality子元素
可以使用loopCardinality子元素中直接指定一個數字
也可以使用loopCardinality子元素中結果為整數的表達式<multiInstanceLoopCharacteristics isSequential="false|true"> <loopCardinality>5</loopCardinality> </multiInstanceLoopCharacteristics>
<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>
- 一種方法是使用loopCardinality子元素
- 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服務任務中間底部區域顯示的補償處理器圖標
XML內容
- 聲明作為補償處理器的節點,需要把isForCompensation設置為true:
<serviceTask id="undoBookHotel" isForCompensation="true" activiti:class="...">
</serviceTask>