天天看點

Activiti工作流架構中任務流程元素詳解!使用任務元素進行任務的排程和執行任務

任務

使用者任務

描述
  • 使用者任務用來設定必須由人員完成的工作
  • 當流程執行到使用者任務,會建立一個新任務,并把這個新任務加入到配置設定人或群組的任務清單中
圖形标記
  • 使用者任務顯示成一個普通任務(圓角矩形),左上角有一個小使用者圖示
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任務(圓角矩形),左上角有一個腳本小圖示
Activiti工作流架構中任務流程元素詳解!使用任務元素進行任務的排程和執行任務
  • 腳本任務定義需要指定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服務任務顯示為圓角矩形,左上角有一個齒輪小圖示
Activiti工作流架構中任務流程元素詳解!使用任務元素進行任務的排程和執行任務
  • 聲明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
      • 引擎會捕獲這個異常,把它轉發到對應的錯誤進行中:邊界錯誤事件或錯誤事件子流程
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服務任務顯示效果一樣(圓角矩形,左上角有一個齒輪小圖示)
Activiti工作流架構中任務流程元素詳解!使用任務元素進行任務的排程和執行任務
  • 要使用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或表達式屬性
  • 業務規則任務是一個圓角矩形,左上角使用一個表格小圖示進行顯示
Activiti工作流架構中任務流程元素詳解!使用任務元素進行任務的排程和執行任務
  • 要執行部署流程定義的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>           
Activiti工作流架構中任務流程元素詳解!使用任務元素進行任務的排程和執行任務

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引擎而言,手工任務是直接通過的活動,流程到達它之後會自動向下執行
  • 手工任務顯示為普通任務(圓角矩形),左上角是一個手型小圖示
Activiti工作流架構中任務流程元素詳解!使用任務元素進行任務的排程和執行任務
<manualTask id="myManualTask" name="Call client for more information" />           

Java接收任務

  • 接收任務是一個簡單任務,會等待對應消息的到達
  • 當流程達到接收任務,流程狀态會儲存到存儲裡.意味着流程會等待在這個等待狀态,直到引擎接收了一個特定的消息,觸發流程穿過接收任務繼續執行
  • 接收任務顯示為一個任務(圓角矩形),右上角有一個消息小标記.消息是白色的(黑色圖示表示發送語義)
Activiti工作流架構中任務流程元素詳解!使用任務元素進行任務的排程和執行任務
<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}" />           
  • 腳本任務監聽器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的變量名
  • 多執行個體的節點,會在節點底部顯示三條短線.三條豎線表示執行個體會并行執行. 三條橫線表示順序執行
Activiti工作流架構中任務流程元素詳解!使用任務元素進行任務的排程和執行任務

  • 要把一個節點設定為多執行個體,節點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子元素指定集合
    <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的流程變量,這個變量會包含集合中的對應元素,上面是用來設定使用者任務的配置設定者
  • 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%的任務完成時,其他任務就會删除,流程繼續執行

邊界事件和多執行個體
  • 多執行個體是一個普通節點,可以在邊緣使用邊界事件
  • 對于中斷型邊界事件,當捕獲事件時,所有激活的執行個體都會銷毀
Activiti工作流架構中任務流程元素詳解!使用任務元素進行任務的排程和執行任務

子流程的所有執行個體都會在定時器觸發時銷毀,無論有多少執行個體,也不論内部節點沒有完成

補償處理器

  • 如果一個節點用來補償另一個節點的業務, 可以聲明為一個補償處理器
  • 補償處理器不包含普通的流,隻在補償事件觸發時執行
  • 補償處理器不能包含進入和外出順序流
  • 補償處理器必須使用直接關聯配置設定給一個補償邊界事件
  • 節點是補償處理器,補償事件圖示會顯示在中間底部區域
  • 補償處理器圖形示例:一個服務任務,附加了一個補償邊界事件,并配置設定了一個補償處理器.注意cancel hotel reservation服務任務中間底部區域顯示的補償處理器圖示
Activiti工作流架構中任務流程元素詳解!使用任務元素進行任務的排程和執行任務
  • 聲明作為補償處理器的節點,需要把isForCompensation設定為true:
<serviceTask id="undoBookHotel" isForCompensation="true" activiti:class="...">
</serviceTask>