Activiti 工作流框架中的任務調度!工作流框架中的任務流程元素詳解,使用監聽器監聽任務執行


任務

用戶任務

描述
  • 用戶任務用來設置必須由人員完成的工作
  • 當流程執行到用戶任務,會創建一個新任務,並把這個新任務加入到分配人或群組的任務列表中
圖形標記
  • 用戶任務顯示成一個普通任務(圓角矩形),左上角有一個小用戶圖標
    在這里插入圖片描述
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內容
  • 腳本任務定義需要指定scriptscriptFormat
<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之前)
    • 可以在腳本中自動保存任何變量,只要把scriptTaskautoStoreVariables屬性設置為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
      • 引擎會捕獲這個異常,把它轉發到對應的錯誤處理中:邊界錯誤事件或錯誤事件子流程
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" />

這里的服務任務有兩個外出順序流:分別叫exceptionno-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定義,但沒有創建itemDefinitionmessage

  • 假設想調用一個名為prettyPrint的方法,必須創建為請求和響應信息對應的messageitemDefinition
<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的異步功能
  • 通過設置camelServiceTaskasync屬性來啟用這個功能
<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}" />
      
  • 腳本任務監聽器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- 特定實例的在循環的索引值
    • 使用activitielementIndexVariable屬性修改loopCounter的變量名
圖形標記
  • 多實例的節點,會在節點底部顯示三條短線.三條豎線表示實例會並行執行. 三條橫線表示順序執行
    在這里插入圖片描述

XML內容

  • 要把一個節點設置為多實例,節點xml元素必須設置一個multiInstanceLoopCharacteristics子元素
<multiInstanceLoopCharacteristics isSequential="false|true">
 ...
</multiInstanceLoopCharacteristics>

isSequential屬性表示節點是進行順序執行還是並行執行

  • 實例的數量會在進入節點時計算一次:
    • 一種方法是使用loopCardinality子元素
      可以使用loopCardinality子元素中直接指定一個數字
      <multiInstanceLoopCharacteristics isSequential="false|true">
         <loopCardinality>5</loopCardinality>
      </multiInstanceLoopCharacteristics>
      
      也可以使用loopCardinality子元素中結果為整數的表達式
      <multiInstanceLoopCharacteristics isSequential="false|true">
         <loopCardinality>${nrOfOrders-nrOfCancellations}</loopCardinality>
      </multiInstanceLoopCharacteristics>
      
    • 另一個方法是通過loopDataInputRef子元素,設置一個類型為集合的流程變量名.對於集合中的每個元素,都會創建一個實例.也可以通過inputDataItem子元素指定集合
      <userTask id="miTasks" name="My Task ${loopCounter}" activiti:assignee="${assignee}">
      	<multiInstanceLoopCharacteristics isSequential="false">
      		 <loopDataInputRef>assigneeList</loopDataInputRef>
               <inputDataItem name="assignee" />
          </multiInstanceLoopCharacteristics>
      </userTask>
      
      假設assigneeList變量包含這些值[kermit, gonzo, foziee],三個用戶任務會同時創建.每個分支都會擁有一個用名為assignee的流程變量,這個變量會包含集合中的對應元素,上面是用來設置用戶任務的分配者
  • loopDataInputRefinputDataItem的缺點:
    • 名字難於記憶
    • 根據BPMN 2.0格式定義 ,不能包含表達式
  • 在activiti中可以在multiInstanceCharacteristics中設置collectionelementVariable
<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>


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM