Activiti_流程定義語言(BPMN)
1、什麼是BPMN
業務流程建模與標註(Business Process Model and Notation,BPMN) ,描述流程的基本符號,包括這些圖元如何組合成一個業務流程圖(Business Process Diagram)
Eclispse畫出流程,有兩個檔案bpmn檔案和png檔案,其中bpmn檔案又可以叫做流程定義檔案,它需要遵循BPMN語言規範.png:就是一個單純的圖片,沒有任何作用.
2、流程(process)
bpmn檔案一個流程的根元素。一個流程就代表一個工作流。
3、順序流
3.1、什麼是順序流
順序流是連線兩個流程節點的連線,代表一個節點的出口。流程執行完一個節點後,會沿著節點的所有外出順序流繼續執行。 就是說,BPMN 2.0預設的行為就是併發的: 兩個外出順序流會創造兩個單獨的,併發流程分支。
順序流主要由4個屬性組成:
Id: 唯一標示,用來區分不同的順序流
sourceRef:連線的源頭節點ID
targetRef:連線的目標節點ID
name(可選):連線的名稱,不涉及業務,主要用於顯示。多出口原則要設定。
說明:
1)結束節點沒有出口
其他節點有一個或多個出口
。如果有一個出口,則代表是一個單線流程;如果有多個出口,則代表是開啟併發流程。
3.2、分支流程-流程圖
3.3、公共程式碼抽取
package cn.itsource.activiti.day02; import org.activiti.engine.ProcessEngine; import org.activiti.engine.ProcessEngines; import org.activiti.engine.RepositoryService; import org.activiti.engine.RuntimeService; import org.activiti.engine.TaskService; import org.activiti.engine.repository.Deployment; import org.activiti.engine.repository.DeploymentBuilder; import org.activiti.engine.runtime.ProcessInstance; import org.activiti.engine.task.Task; public class BaseBpmn { private ProcessEngine processEngine=ProcessEngines.getDefaultProcessEngine(); //自己類和子類都可能使用:使用protected修飾 protected RepositoryService repositoryService = processEngine.getRepositoryService(); protected RuntimeService runtimeService = processEngine.getRuntimeService(); protected TaskService taskService = processEngine.getTaskService(); /** * * @param name 部署流程的名字 * @param resourceName 載入資源的名字字首 * @return */ protected Deployment deploy(String name, String resourceName) { //建立核心 //獲取服務 //做事情 // this.getClass().getClassLoader().getResourceAsStream("LeaveFlow.bpmn");從classpath下面載入 // this.getClass().getResourceAsStream("/LeaveFlow.bpmn");//從classpath下面載入 // this.getClass().getResourceAsStream("LeaveFlow.bpmn");//從當前類當前包載入(採納) // this.getClass().getResourceAsStream("./LeaveFlow.bpmn");//從當前類當前包載入(採納) String resourceNameBpmn=resourceName+".bpmn"; String resourceNamePng=resourceName+".png"; DeploymentBuilder deploymentBuilder = repositoryService.createDeployment(); deploymentBuilder.name(name) .addInputStream(resourceNameBpmn, this.getClass().getResourceAsStream(resourceNameBpmn)) .addInputStream(resourceNamePng, this.getClass().getResourceAsStream(resourceNamePng)); Deployment deployment = deploymentBuilder.deploy(); return deployment; } /** * * @param processDefinitionKey 啟動流程的定義的key */ protected ProcessInstance startProcess(String processDefinitionKey) { return runtimeService.startProcessInstanceByKey(processDefinitionKey); } /** * 在一個流程例項中,一個辦理人只有一個唯一的任務 * @param processInstanceId 流程例項id * @param assignee 辦理人 * @return */ protected Task queryPersonalTask(String processInstanceId, String assignee) { return taskService.createTaskQuery() .processInstanceId(processInstanceId) .taskAssignee(assignee) .singleResult(); } }
3.4、分支流程-測試程式碼
3.4.1、輔助程式碼:
@Test
public void deployTest() throws Exception {
Deployment deployment = deploy("報銷申請","SequesceFlowTest");
System.out.println("deploymentId:"+deployment.getId());
}
@Test
public void startProcessTest() throws Exception {
String processDefinitionKey="SequesceFlowTest";
ProcessInstance processInstance = startProcess(processDefinitionKey);
System.out.println("ProcessInstanceId:"+processInstance.getId());//2501
}
3.4.2、測試駁回:
//測試駁回
/**
* ①:先完成報銷申請,
* ②:走到審批的時候,設定一個flag的流程變數為flase,駁回
* ③:回到①,在完成報銷申請
* ④:審批人又得到審批審批任務
* @throws Exception
*/
@Test
public void notPassTest() throws Exception {
//①:先完成報銷申請,
String processInstanceId="2501";
String assignee="小明";
Task applyTask = queryPersonalTask(processInstanceId,assignee);
System.out.println("獲取申請任務:"+applyTask);
//先完成報銷申請
taskService.complete(applyTask.getId());
assignee="小剛";
Task approveTask = queryPersonalTask(processInstanceId,assignee);
System.out.println("獲取審批任務:"+applyTask);
// ②:走到審批的時候,設定一個flag的流程變數為flase
taskService.setVariable(approveTask.getId(),"flag", "false");
//駁回
taskService.complete(approveTask.getId());
//④:審批人又得到審批審批任務
assignee="小明";
applyTask = queryPersonalTask(processInstanceId,assignee);
System.out.println("再次獲取申請任務:"+applyTask);
}
3.4.3測試通過:
/**
* 通過
* @throws Exception
*/
@Test
public void passTest() throws Exception {
String processInstanceId="2501";
String assignee="小剛";
Task approveTask = queryPersonalTask(processInstanceId,assignee);
System.out.println("獲取審批任務:"+approveTask);
// ②:走到審批的時候,設定一個flag的流程變數為flase
taskService.setVariable(approveTask.getId(),"flag", "true");
//通過
taskService.complete(approveTask.getId());
}
4、節點
開始節點
結束節點:加一個執行監聽器,修改流程的狀態
任務節點
閘道器節點
監聽器節點
4.1、開始事件節點(startEvent)
開始事件對應節點
4.2、結束事件節點(endEvent)
結束事件對應節點
4.3、任務節點 (Task)
4.3.1、接收任務節點 (receiveTask)
接收任務是一個簡單任務,它會等待對應訊息的到達。 當前,官方只實現了這個任務的java語義。 當流程達到接收任務,流程狀態會儲存到資料庫中。
在任務建立後,意味著流程會進入等待狀態, 直到引擎接收了一個特定的訊息, 這會觸發流程穿過接收任務繼續執行。(短跑比賽一樣,預備,訊號觸發跑)
圖形標記:
接收任務顯示為一個任務(圓角矩形),右上角有一個訊息小標記。 訊息是白色的(黑色圖標表示傳送語義):
1、流程圖
銷售經理統計當前的營業額,然後簡訊傳送給老闆
2、測試程式碼
公共程式碼增加:
/** * 通過流程例項Id獲取流程例項 * @param processInstanceId * @return */ protected ProcessInstance queryProcessInstanceById(String processInstanceId) { return runtimeService.createProcessInstanceQuery() .processInstanceId(processInstanceId) .singleResult(); } /** * 在一次流程中通過活動id查詢唯一執行物件 * * @param pid * @param calcTotalPriceActivityId * @return */ protected Execution queryExecution(String pid, String calcTotalPriceActivityId) { return runtimeService.createExecutionQuery() .processInstanceId(pid) .activityId(calcTotalPriceActivityId) .singleResult(); }
第一個節點:今日銷售額計算
// 第一個節點:計算今日銷售額 @Test public void testCalcTotalPrice() throws Exception { // 0 查詢到"計算今日銷售額"的執行物件 String pid = "2501"; String calcTotalPriceActivityId = "當天營業額Id"; Execution calcTotalPriceExecution = queryExecution(pid, calcTotalPriceActivityId); System.out.println("獲取當天營業額的執行物件!" + calcTotalPriceExecution.getActivityId()); // 1 計算今日銷售額 double totalPrice = 666666.66666d; System.out.println("計算今日銷售額為:" + totalPrice); // 2 把銷售額放入流程變數,共享給下一個節點 Map<String, Object> processVariables = new HashMap<>(); processVariables.put("totalPrice", totalPrice); // 3 發訊息觸發"計算今日銷售額"執行物件往下走 System.out.println("設定流程變數,並讓它往下走!"); runtimeService.signal(calcTotalPriceExecution.getId(), processVariables); // 4 可以獲取下一個節點對應的執行對 String sendMsgActivityId = "簡訊傳送給老闆Id"; Execution sendMsgExecution = queryExecution(pid, sendMsgActivityId); System.out.println("獲取到第二個節點:" + sendMsgExecution.getActivityId()); if (sendMsgExecution != null) { System.out.println("第一個節點已經處理完畢!"); } }
第二個節點:簡訊傳送給老闆
@Test public void testSendMsg() throws Exception { String pid = "2501"; String sendMsgActivityId = "簡訊傳送給老闆Id"; Execution sendMsgExecution = queryExecution(pid, sendMsgActivityId); // 1 從流程變數種獲取銷售額 String executionId = sendMsgExecution.getId(); Double totalPrice = runtimeService.getVariable(executionId, "totalPrice", Double.class); System.out.println("從流程變數中獲取今日銷售額:" + totalPrice); // 2 把銷售額傳送給老闆 System.out.println("傳送簡訊給老闆:今日收穫不錯,營業額為" + totalPrice); // 3 讓流程繼續往下走 runtimeService.signal(executionId); // 4 判斷流程結束 ProcessInstance processInstance = queryProcessInstanceById(pid); if (processInstance == null) { System.out.println("流程已結束!"); } }
4.3.2使用者任務節點 (userTask)
使用者任務用來設定必須由人員完成的工作。 當流程執行到使用者任務,會建立一個新任務, 並把這個新任務加入到分配人或群組的任務列表中。
圖形標記
使用者任務顯示成一個普通任務(圓角矩形),左上角有一個小使用者圖示。
任務分配
使用者任務的辦理都需要人工的參與。使用者任務可以分為兩大類。私有任務(待辦任務)和公有任務(可接任務)。
私有任務
私有任務即有直接分配給指定使用者的任務。只有一個使用者可以成為任務 的執行者。 在activiti中,使用者叫做執行者。 擁有執行者的使用者任務 (即私有任務)對使用者是不可見的。
辦理者指定分三種方式:
- 寫死
- 流程變數
- 監聽器
公共程式碼增加:
/**
* 通過流程定義key啟動流程並且設定流程變數
* @param processDefinitionKey
* @return
*/
protected ProcessInstance startProcess(String processDefinitionKey,Map<String, Object> variables) {
return runtimeService.startProcessInstanceByKey(processDefinitionKey, variables);
}
私有任務測試:
/**
* 私有任務測試
* 辦理者指定:
* 1)寫死,設計流程時寫死辦理者(不用)
* 缺點:這個流程只能由寫死哪個人申請
* 2)流程變數設定--第一個節點
* ①設計流程時要從流程變數中獲取辦理者
* ②流程執行時要設定對應流程變數-啟動時設定
* 問題:如果第二個-n個節點通過在啟動流程時設定變數來指定辦理者,
* 第一個辦理長時間沒有辦理,在這期間後面節點辦理者離職了,走下去再離職人員手上還會掛任務.
* 方案1:下一個任務辦理時再指定下一個任務辦理者.不好,業務邏輯耦合
* 方案2:使用監聽器設定下一個節點啟動時指定人,不需要耦合.(採納)
* 3)監聽器--第二個-n個節點
* ①寫一個類實現監聽器類-TaskListener
* ②把寫好類繫結到對應節點
* ③實現監聽器邏輯
* @author Administrator
*
*/
public class PersonalTaskTest extends BaseBpmnTest {
//寫死
@Test
public void test1() {
//部署
deploy("私有任務測試1", "PersonalTaskTest");
//啟動
String processDefinitionKey = "PersonalTaskTest";
ProcessInstance pi = startProcess(processDefinitionKey );
//獲取寫死辦理的任務
String assignee = "小鑫鑫";
Task task = queryPersonalTask(pi.getId(), assignee );
System.out.println("id:"+task.getId()
+",name:"+task.getName()+",assignee:"+task.getAssignee());
}
@Test
public void test2() {
//部署
//deploy("私有任務測試2", "PersonalTaskTest2");
//啟動時要設定第一個節點的辦理者
String processDefinitionKey = "PersonalTaskTest2";
Map<String, Object> variables = new HashMap<>();
//第一個節點辦理者
String assignee = "曉鑫鑫";//當前登入使用者名稱稱
variables.put("apply", assignee);
ProcessInstance pi = startProcess(processDefinitionKey,variables );
System.out.println("pid:"+pi.getId());
//獲取寫死辦理的任務
Task task = queryPersonalTask(pi.getId(), assignee );
System.out.println("id:"+task.getId()
+",name:"+task.getName()+",assignee:"+task.getAssignee());
}
//要把第一個節點走下去
/**
* 使用流程變數設定
* @throws Exception
*/
@Test
public void test3Pre() throws Exception {
//部署
//deploy("私有任務測試3", "PersonalTaskTest3");
//啟動時要設定第一個節點的辦理者
String processDefinitionKey = "PersonalTaskTest3";
Map<String, Object> variables = new HashMap<>();
//第一個節點辦理者
String assignee = "逗比鑫";//當前登入使用者名稱稱
variables.put("apply", assignee);
ProcessInstance pi = startProcess(processDefinitionKey,variables );
System.out.println("pid:"+pi.getId());
//獲取寫死辦理的任務
Task task = queryPersonalTask(pi.getId(), assignee );
System.out.println("id:"+task.getId()
+",name:"+task.getName()+",assignee:"+task.getAssignee());
taskService.complete(task.getId());
}
//上一個任務完成設定下一個任務的辦理者,沒法做,沒有辦法直接獲取下一個任務並設定
//當前任務建立時,再指定辦理者
@Test
public void test3() throws Exception {
String assignee = "逗比鑫"+"審批人";
String processInstanceId = "5001";
Task task = queryPersonalTask(processInstanceId, assignee );
System.out.println("id:"+task.getId()
+",name:"+task.getName()+",assignee:"+task.getAssignee());
//完成第二個任務
taskService.complete(task.getId());
//判斷流程是否結束
ProcessInstance pi = queryProcessInstanceById(processInstanceId);
if (pi == null) {
System.out.println("流程結束");
}
}
}
在下一個節點建立時,增加這個監聽器:
import org.activiti.engine.delegate.DelegateTask;
import org.activiti.engine.delegate.TaskListener;
/**
* ①寫一個類實現監聽器類-TaskListener
* ②把寫好類繫結到對應節點
* ③實現監聽器邏輯
* @author Administrator
*
*/
public class ManagerSettingListener implements TaskListener{
@Override
public void notify(DelegateTask delegateTask) {
System.out.println(delegateTask.getName());
//1 獲取申請人
String apply = delegateTask.getVariable("apply", String.class);
//2 通過申請人獲取審批人 通過申請人獲取部門經理
String manager = apply+"審批人";
//3 設定辦理者
delegateTask.setAssignee(manager);
//delegateTask.addCandidateUser(userId);
//delegateTask.addCandidateGroup(groupId);
}
}
公共任務
有的使用者任務在指派時無法確定具體的辦理者,這時任務也可以加入到人員的候選任務列表中,然後讓這些人員選擇性認領和辦理任務。
公有任務的分配可以分為指定候選使用者和候選組兩種。
每一種都要三種指定方式
- 寫死(不用的)
- 流程變數指定第一個節點
- 任務監聽器指定第2-n節點
a) 把任務新增到一批使用者的候選任務列表中,使用candidateUsers 屬 性,XML內容如下:
<userTaskid="theTask"name="my task"activiti:candidateUsers="sirius,kermit"/>
candidateUsers屬性內為使用者的ID,多個使用者ID之間使用(半形)逗號間隔。
b)把任務新增到一個或多個候選組下,這時任務對組下的所有使用者可 見,首先得保證每個組下面有使用者,通過IdentityService物件建立使用者 和組,然後把使用者新增到對應的組下,java程式碼如下:
上述兩個雖然都可以統稱為任務節點,但是還是有本質區別:
- receiveTask主要代表機器自動執行的,userTask代表人工干預的。
- receiveTask任務產生後會在act_ru_execution表中新增一條記錄, 而userTask產生後會在act_ru_execution和act_ru_task(主要記錄任 務的釋出時間,辦理人等資訊)中各產生一條記錄。
receiveTask任務提交方式使用RuntimeService的signal方法提交, userTask任務提交方式使用TaskService的complete方法提交。
5、閘道器節點
閘道器用來控制流程的流向。
閘道器顯示成菱形圖形,內部有有一個小圖示。 圖標表示閘道器的型別。
5.1、排他閘道器(ExclusiveGateWay)
簡單理解有多個分支,但是隻能走一個分支。
排他閘道器(也叫異或(XOR)閘道器,或更技術性的叫法 基於資料的排他閘道器), 用來在流程中實現決策。
圖形標記
排他閘道器顯示成一個普通閘道器(比如,菱形圖形), 內部是一個“X”圖示,表示異或(XOR)語義。 注意,沒有內部圖示的閘道器,預設為排他閘道器。 BPMN 2.0規範不允許在同一個流程定義中同時使用沒有X和有X的菱形圖形。
XML內容
排他閘道器的XML內容是很直接的:用一行定義了閘道器, 條件表示式定義在外出順序流中。 參考條件順序流 獲得這些表示式的可用配置。
它對應的XML內容如下:
<exclusiveGateway id="exclusivegateway1" name="Exclusive Gateway" default="財務"></exclusiveGateway>
<sequenceFlow id="財務" name="小於1000" sourceRef="exclusivegateway1" targetRef="usertask2">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${price<1000}]]></conditionExpression>
</sequenceFlow>
<userTask id="usertask3" name="部門經理審批"></userTask>
<sequenceFlow id="部門經理" name="大於1000小於10000" sourceRef="exclusivegateway1" targetRef="usertask3">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${price>1000&&price<10000}]]></conditionExpression>
</sequenceFlow>
<userTask id="usertask4" name="老闆審批"></userTask>
<sequenceFlow id="老闆" name="大於10000" sourceRef="exclusivegateway1" targetRef="usertask4">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${price>10000}]]></conditionExpression>
</sequenceFlow>
說明:
1.一個排他閘道器對應一個以上的順序流
2.由排他閘道器流出的順序流都有個conditionExpression元素,在內部維護返回boolean型別的決策結果。
3.決策閘道器只會返回一條結果。當流程執行到排他閘道器時,流程引擎會自動檢索網關出口,從上到下檢索如果發現第一條決策結果為true或者沒有設定條件的(預設為成立),則流出。
4.如果沒有任何一個出口符合條件則丟擲異常。
5.2、並行閘道器(parallelGateWay)
多個分支要一起執行完,才算完成(會籤)。
閘道器也可以表示流程中的並行情況。最簡單的並行閘道器是parallelGateWay,它允許將流程 分成多條分支,也可以把多條分支 匯聚到一起。
圖形標記
並行閘道器顯示成一個普通閘道器(菱形)內部是一個“加號”圖示, 表示“與(AND)”語義。
XML內容
定義並行閘道器只需要一行XML:
<parallelGateway id="parallelgateway1" name="Parallel Gateway"></parallelGateway>
實際發生的行為(分支,聚合,同時分支聚合), 要根據並行閘道器的順序流來決定。
參考如下程式碼:
<startEvent id="theStart"/>
<sequenceFlow id="flow1"sourceRef="theStart"targetRef="fork"/> <parallelGatewayid="fork"/> <sequenceFlowsourceRef="fork"targetRef="receivePayment"/> <sequenceFlowsourceRef="fork"targetRef="shipOrder"/>
<userTaskid="receivePayment"name="Receive Payment"/> <sequenceFlowsourceRef="receivePayment"targetRef="join"/>
<userTaskid="shipOrder"name="Ship Order"/> <sequenceFlowsourceRef="shipOrder"targetRef="join"/> <parallelGatewayid="join"/> <sequenceFlowsourceRef="join"targetRef="archiveOrder"/>
<userTaskid="archiveOrder"name="Archive Order"/> <sequenceFlowsourceRef="archiveOrder"targetRef="theEnd"/>
<endEventid="theEnd"/>
上面例子中,流程啟動之後,會建立兩個任務:
ProcessInstancepi =runtimeService.startProcessInstanceByKey("forkJoin"); TaskQueryquery=taskService.createTaskQuery() .processInstanceId(pi.getId()) .orderByTaskName().asc(); List<Task>tasks =query.list(); assertEquals(2,tasks.size()); Task task1 =tasks.get(0); assertEquals("Receive Payment",task1.getName());Tasktask2 =tasks.get(1); assertEquals("Ship Order",task2.getName());
當兩個任務都完成時,第二個並行閘道器會匯聚兩個分支,因為它只有一條外出連線, 不會建立並行分支, 只會建立歸檔訂單任務。
說明:
1.並行閘道器的功能是基於進入和外出的順序流的:
分支(fork): 並行後的所有外出順序流,為每個順序流都建立一個併發分支。
匯聚(join): 所有到達並行閘道器,在此等待的進入分支, 直到所有進入順序流的分支都到達以後, 流程就會通過匯聚閘道器。
2.並行閘道器的進入和外出都是使用相同節點標示
3.如果同一個並行閘道器有多個進入和多個外出順序流, 它就同時具有分支和匯聚功能。 這時,閘道器會先匯聚所有進入的順序流,然後再切分成多個並行分支。
4.並行閘道器不會解析條件。 即使順序流中定義了條件,也會被忽略。
並行閘道器不需要是“平衡的”(比如, 對應並行閘道器的進入和外出節點數目相等)。如圖中標示是合法的:
6、監聽器(Listener)
在流程中我們有時會對整個流程或者一個節點的某種狀態做出相應的處理。這時就會用到監聽器。
在Activiti中流程的監聽主要分為兩大類,執行監聽器和任務監聽器。
任務監聽器:只能繫結到使用者任務節點。 可用於使用者節點辦理者的動態設定,操作日誌等
執行監聽器:可以在任何節點繫結。修改流程結束狀態(在結束節點繫結執行監聽器執行修改),操作日誌等
6.1、執行監聽器(ExecutionListener)
執行監聽器可以執行外部java程式碼或執行表示式,當流程定義中發生了某個事件。 可以捕獲的事件有:
- 流程例項的啟動和結束。
- 選中一條連線。
- 節點的開始和結束。
- 閘道器的開始和結束。
- 中間事件的開始和結束。
- 開始時間結束或結束事件開始。
現在有這樣一個簡單流程,只包含開始、結束、接收任務和使用者任務4個節點:
配置監聽器,XML程式碼如下:
說明:
任務監聽器支援以下屬性:
- event(必選):任務監聽器會被呼叫的任務型別。 可能的型別為:
- start:流程節點建立後觸發。
- end:當任務完成,並尚未從執行資料中刪除時觸發。
- take:任務完成後,流程流出時觸發
- class:必須呼叫的代理類。 這個類必須實現org.activiti.engine.delegate.
ExecutionListener介面。實現類程式碼如下:
執行監聽器配置可以放在以下三個地方,如圖:
a)監聽整個流程的啟動和結束狀態,配置為process節點的子元素,如圖①
b)監聽一個節點的啟動和結束狀態,配置為一個節點的子元素,如圖②和③
c)監聽一條連線的執行,配置在sequenceFlow節點的內部,只有task一種事件,如圖④
啟動流程測試程式碼如下:
結果如下:
6.2、任務監聽器(TaskListener)
任務監聽器可以在發生對應的任務相關事件時執行自定義java邏輯 或表示式。
任務監聽器只能新增到流程定義中的使用者任務中。在之前任務節點上新增任務監聽:
任務監聽器可以在發生對應的任務相關事件時執行自定義java邏輯 或表示式。
任務監聽器只能新增到流程定義中的使用者任務中。在之前任務節點上新增任務監聽:
說明:
任務監聽器支援以下屬性:
- event(必選):任務監聽器會被呼叫的任務型別。 可能的型別為:
- create:任務建立並設定所有屬性後觸發。
- assignment:任務分配給一些人時觸發。 當流程到達userTask,assignment事件 會在create事件之前發生。 這樣的順序似乎不自然,但是原因很簡單:當獲得create時間時, 我們想獲得任務的所有屬性,包括執行人。
- complete:當任務完成,並尚未從執行資料中刪除時觸發。
- class:必須呼叫的代理類。 這個類必須實現org.activiti.engine.delegate.TaskListener 介面。Java程式碼如下:
執行測試程式碼得到結果:
流程結束,日誌內容為:[Start start, Receive Task start, Receive Task end, Receive Task take, User Task start, User Task assignment, User Task create, User Task complete, User Task end, End end]
新新增的任務監聽包裹在executionListener監聽的內部,順序為:execution Start--> task Assignment-->task Create-->task Complete-->execution End-->execution take。