【Activiti】從入門到放棄——流程定義語言(BPMN)
什麼是BPMN
業務流程建模與標註(Business Process Model and Notation,BPMN) ,描述流程的基本符號,包括這些圖元如何組合成一個業務流程圖(Business Process Diagram)
Eclispse畫出流程,有兩個檔案bpmn檔案和png檔案,其中bpmn檔案又可以叫做流程定義檔案,它需要遵循BPMN語言規範.png:就是一個單純的圖片,沒有任何作用.
流程(process)
bpmn檔案一個流程的根元素。一個流程就代表一個工作流。
順序流(sequenceFlow )
1.什麼是順序流
順序流是連線兩個流程節點的連線,代表一個節點的出口。流程執行完一個節點後,會沿著節點的所有外出順序流繼續執行。 就是說,BPMN 2.0預設的行為就是併發的: 兩個外出順序流會創造兩個單獨的,併發流程分支。
順序流主要由4個屬性組成:
Id: 唯一標示,用來區分不同的順序流
sourceRef:連線的源頭節點ID
targetRef:連線的目標節點ID
name(可選):連線的名稱,不涉及業務,主要用於顯示。多出口原則要設定。
說明:
1)結束節點沒有出口
其他節點有一個或多個出口。如果有一個出口,則代表是一個單線流程;如果有多個出口,則代表是開啟併發流程。
2.分支流程-流程圖
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(); } }
4.分支流程-測試程式碼
輔助程式碼:
@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 }
測試駁回:
//測試駁回
/**
* ①:先完成報銷申請,
* ②:走到審批的時候,設定一個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);
}
測試通過:
/**
* 通過
* @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());
}
節點
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("流程已結束!");
}
}
閘道器節點
簡單理解有多個分支,但是隻能走一個分支。
配置檔案
<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.如果沒有任何一個出口符合條件則丟擲異常。
監聽器
執行監聽
配置檔案:
實現類程式碼如下
執行監聽器配置可以放在以下三個地方,如圖:
啟動流程測試程式碼如下:
任務監聽:
配置檔案:
說明:
1.任務監聽器支援以下屬性:
event(必選):任務監聽器會被呼叫的任務型別。 可能的型別為:
create:任務建立並設定所有屬性後觸發。
assignment:任務分配給一些人時觸發。 當流程到達userTask,assignment事件 會在create事件之前發生。 這樣的順序似乎不自然,但是原因很簡單:當獲得create時間時, 我們想獲得任務的所有屬性,包括執行人。
complete:當任務完成,並尚未從執行資料中刪除時觸發。
class:必須呼叫的代理類。 這個類必須實現org.activiti.engine.delegate.TaskListener 介面。Java程式碼如下:
2.執行測試程式碼得到結果:
流程結束,日誌內容為:[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。