一、流程
在JPDL中process元素是每個流程定義的頂級元素,即任何流程定義都必須以如下形式開始和結束
<process>
...
</process>
process元素擁有的屬性:
屬性 | 類型 | 預設值 | 是否必須 | 描述 |
name | 文本 | 無 | 必須 | 展示給使用者 |
key | 如省略,則根據name生成 | 辨別不同流程 | ||
version | 整型 | 從1開始 | 同一流程的不同版本 |
它下的子元素有:description、activities
二、流轉控制活動
- start——開始活動
- state——狀态活動
- decision——判斷活動
- fork--join——分支/聚合活動
- end——結束活動
- task——人工任務活動
- sup-process——子流程活動
- custom——自定義活動
1.start
即流程的入口,一個流程中必須擁有一個start,必須有一個流出轉移(transition),這個轉移會在流程中通過start活動的時候執行。
2.state(狀态活動)
當業務流程受到某些特定的外部幹預後再繼續運作,而在這之前流程處于一個中斷等待的狀态,這個時候就是state活動。
示例:
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIyZwpmL5MzM0cUWEhEMVJVQBFULElja5VXL312ax02bpt0dvwlQF9CX2kzLcJDMN9CXyAzcml3dvwVbvNmLvR3YxUjL3M3Lc9CX6MHc0RHaiojIsJye.jpg)
對應的JPDL如下:
<process name="stateSequence" xmlns="http://jbpm.org/4.4/jpdl">
<!-- 流程開始後轉移到a state -->
<start name="start">
<transition to="a"/>
</start>
<!-- a state轉移到b state -->
<state name="a">
<transition to="b"/>
</state>
<!-- b state轉移到c state -->
<state name="b">
<transition to="c"/>
</state>
<!-- 最終停留在c state -->
<state name="c"/>
</process>
根據此流程定義發起執行個體:
ProcessInstance processInstance =
executionService.startProcessInstanceByKey("stateSequence");
//在沒有任何外部觸發的情況下,此流程會在a state一直等待,調用singalXx會觸發流程進入下一步
Execution executionInA = processInstance.findActiveExecutionIn("a");
//斷言流程執行個體在a state等待
assertNotNull(executionInA);
//發出觸發執行的信号
processInstance = executionService.signalExecutionById(executionInA.getId());
Execution executionInB = processInstance.findActiveExecutionIn("b");
//斷言流程執行個體走向下一步b state
assertNotNull(executionInB);
//繼續觸發
processInstance = executionService.signalExecutionById(executionInB.getId());
//...c
在state活動裡可以定義多個transition元素,通過觸發指定轉移路徑的名稱,可以選擇其中的一個transition通過。
<process name="stateChice" xmlns="http://jbpm.org/4.4/jpdl">
<!-- 流程開始後轉移到wait for response state -->
<start>
<transition to="wait for response"/>
</start>
<!-- 這裡有兩個transaction供選擇,二者選一 -->
<state name="wait for response">
<transition name="accept" to="submit doc"/>
<transition name="reject" to="try again"/>
</state>
<!-- 此活動為accept的transition -->
<state name="submit doc"/>
<!-- 此活動為reject的transition -->
<state name="submit doc"/>
</process>
要想該流程運作起來,則需要:
ProcessInstance processInstance =
executionService.startProcessInstanceByKey("stateChoice");
//假設該流程到達了wait for response,則該執行個體會一直等待外部觸發的出現
//獲得流程執行個體的ID
String executionId = processInstance.findActiveExecutionIn("wait for response").getId();
//觸發accept信号
processInstance = executionService.signalExecutionById(executionId,"accept");
//斷言流程執行個體流向了預期的活動
assertTrue(processInstance.isActive("submit doc"));
//...同理适用于reject transition
3.decision(判斷活動)
根據條件在多個流轉路徑中選擇其一通過,也就是做一個決定性的判斷,這時候使用decision。
decision可以擁有多個流出轉移,當流程執行個體到達decision活動時,會根據最先比對成功的一個條件自動地通過響應的流出轉移。
decision流向哪個轉移,有3種方式。
1>使用decision活動的condition元素
當一個transition的condition值為true或者一個沒有設定condition的transition,那麼該流程就立刻流向這個transition。
對應的JPDL:
<process name="decisionConditions" xmlns="http://jbpm.org/4.4/jpdl">
<start>
<transition to="evaluate document"/>
</start>
<decision name="evaluate document">
<!-- 以下是兩個流轉條件表達式 -->
<transition to="submit doc">
<!-- 變量content等于good -->
<condition expr="#{content=='good'}"/>
</transition>
<transition to="try again">
<!-- 變量content等于bad -->
<condition expr="#{content=='bad'}"/>
</transition>
<!-- 無條件轉移 -->
<transition to="give up"/>
</decision>
<state name="submit doc"/>
<state name="try again"/>
<state name="give up"/>
</process>
測試代碼如下:
Map<String, Object> variables = new HashMap<>();
variables.put("content", "good");
//發起流程執行個體并傳入流程變量
ProcessInstance processInstance = executionService
.startProcessInstanceById("decisionCondition", variables);
//斷言
assertTrue(processInstance.isActive("submit doc"));
2>使用decision活動的expr屬性
expr屬性來判斷流程的轉向,需要指定流轉的路徑
<process name="decisionExpression" xmlns="http://jbpm.org/4.4/jpdl">
<start>
<transition to="evaluate document"/>
</start>
<!-- #{content}即為判斷表達式 -->
<decision expr="#{content}" name="evaluate document">
<!-- content值為good時的轉移 -->
<transition name="good" to="submit doc"/>
<!-- content值為bad時的轉移 -->
<transition name="bad" to="try again"/>
<!-- content值為ugly時的轉移 -->
<transition name="ugly" to="give up"/>
</decision>
<state name="submit doc"/>
<state name="try again"/>
<state name="give up"/>
</process>
單元測試如下(同上):
Map<String, Object> variables = new HashMap<>();
variables.put("content", "good");
//發起流程執行個體并傳入流程變量
ProcessInstance processInstance = executionService
.startProcessInstanceById("decisionCondition", variables);
//斷言
assertTrue(processInstance.isActive("submit doc"));
3>使用decision活動的handler元素
當判斷流轉時計算大量、複雜的業務邏輯時,可以實作DecisionHandler接口
DecisionHandler接口
public interface DecisionHandler extends Serializable {
//提供流程執行個體的執行上下文(execution)作為參數,傳回字元串類型的轉移名稱
String decide(OpenExecution execution);
}
此時handler需要作為decision活動的子元素進行配置。
對應的jPDL如下:
<process name="decisionHandler" xmlns="http://jbpm.org/4.4/jpdl">
<start name="start">
<transition to="evaluate document"/>
</start>
<decision name="evaluate document">
<!-- 所有的流轉處理邏輯都委派給ContentEvaluation -->
<handler class="test.TestHandler"/>
<transition name="good" to="submit doc"/>
<transition name="bad" to="try again"/>
<transition name="ugly" to="give up"/>
</decision>
<state name="submit doc"/>
<state name="try again"/>
<state name="give up"/>
</process>
對應的TestHandler如下:
public class TestHandler implements DecisionHandler {
@Override
public String decide(OpenExecution execution) {
//獲得流程變量content
String content = (String)execution.getVariable("content");
if ("great".equals(content)) {
return "good";
}
if ("improve".equals(content)) {
return "bad";
}
return "ugly";
}
}
對應的單元測試如下:
Map<String, Object> variables = new HashMap<>();
variables.put("content", "great");
//發起流程執行個體并傳入流程變量
ProcessInstance processInstance = executionService
.startProcessInstanceById("decisionHandler", variables);
//斷言
assertTrue(processInstance.isActive("submit doc"));
注意:decision與state的差別!
decision活動定義的流轉條件沒有任何一個得到滿足,那麼流暢執行個體将無法進行下去會抛出異常。
state活動有多個流出轉移,且同樣沒有任何一個得到滿足,那麼會從定義的第一條流轉出去。
結論:decision具有更加嚴格的判斷,如不定義預設路徑,當無法滿足條件時則報錯
4.fork-join(分支/聚合活動)
當我們需要流程并發執行的時候,就需要使用到fork-join活動的組合,fork可以使流程在一條主幹上出現并行的分支,join則可以使流程的并發分支聚合成一條主幹。
對應的jPDL:
<process name="concurrency" xmlns="http://jbpm.org/4.4/jpdl">
<start name="start1" g="57,199,48,48">
<transition to="fork"/>
</start>
<!--流程在此産生3個并行的分支 -->
<fork name="fork" g="179,198,48,48">
<transition to="send invoice"/>
<transition to="load truck" g="205,322:"/>
<transition to="print doc" g="201,130:"/>
</fork>
<state name="send invoice" g="315,105,92,52">
<transition to="final join" g="664,130:"/>
</state>
<state name="load truck" g="318,196,92,52">
<transition to="shipping join" g="503,220:"/>
</state>
<state name="print doc" g="316,296,92,52">
<transition to="shipping join" g="511,323:"/>
</state>
<!-- 分支活動load truck和print doc在此聚合 -->
<join name="shipping join">
<transition to="drive truck"/>
</join>
<state name="drive truck">
<transition to="final join"/>
</state>
<!-- 最終聚合 -->
<join name="final join">
<transition to="end"/>
</join>
<end name="end" />
</process>
單元測試執行上述流程定義:
//發起流程執行個體
ProcessInstance processInstance = executionService
.startProcessInstanceByKey("concurrency");
String pid = processInstance.getId();
//構造一個活動集合以驗證分支
Set<String> activitys = new HashSet<>();
activitys.add("send invoice");
activitys.add("load truck");
activitys.add("print doc");
//斷言目前活動即為産生的3個分支
assertEquals(activitys, processInstance.findActiveActivityNames());
//發出執行信号通過"send invoice"活動,這個時候流程會在final join等待其他分支
String sendInvoice = processInstance.findActiveExecutionIn("send invoice").getId();
processInstance = executionService
.signalExecutionById(sendInvoice);
//...在活動名稱集合中排除"send invoice"活動
activitys.remove("send invoice");
//此時,仍然可以斷言另外2個分支還在等待
assertNotNull(processInstance.findActiveExecutionIn("load truck"));
assertNotNull(processInstance.findActiveExecutionIn("print doc"));
//發出執行信号通過剩下的第1個分支——load truck活動
String loadTruck = processInstance.findActiveExecutionIn("load truck").getId();
processInstance = executionService.signalExecutionById(loadTruck);
//....在活動中排除"load truck"
activitys.remove("load truck");
//發出執行信号——print doc活動
String printDoc = processInstance
.findActiveExecutionIn("print doc").getId();
processInstance = executionService.signalExecutionById(printDoc);
//...在活動中排除"print doc"
activitys.remove("print doc");
//斷言通過第一個活動shipping join,到達了drive truck
activitys.add("drive truck");
assertEquals(activitys, processInstance.findActiveActivityNames());
assertNotNull(processInstance.findActiveExecutionIn("drive truck"));
//發出執行信号通過"drive truck"
String driveTruck = processInstance.findActiveExecutionIn("drive truck").getId();
processInstance = executionService.signalExecutionById(driveTruck);
//最終聚合"final join"
//是以斷言此流程已經不存在了
assertNull("流程:"+pid+"不存在,"+executionService.findExecutionById(pid));
5.end(結束活動)
預設情況下,當流程執行個體運作到end活動會結束,但是在到達end活動的流程執行個體中仍然活躍的流程活動将會被保留繼續執行。
簡單流程定義:
<process name="concurrency" xmlns="http://jbpm.org/4.4/jpdl">
<start g="57,199,48,48">
<transition to="end"/>
</start>
<end name="end" g="243,197,48,48"/>
</process>
單元測試如下:
//發起流程執行個體
ProcessInstance processInstance = executionService
.startProcessInstanceByKey("concurrency");
//建立流程執行個體後,直接斷言其結束
assertTrue(processInstance.isEnded());
複雜一些的end活動
一個流程定義可以有多個end活動,以便通過事件機制觸發不同的結束方式。
<process name="concurrency" xmlns="http://jbpm.org/4.4/jpdl">
<start g="57,199,48,48">
<transition to="get code"/>
</start>
<!-- 此活動有3條流出轉移可選擇 -->
<state name="get code" g="213,196,92,52">
<transition name="200" to="ok" g="258,135:-33,-5"/>
<transition name="400" to="bad request" g="-35,-17"/>
<transition name="500" to="server error" g="261,303:-35,-22"/>
</state>
<end name="ok" g="423,112,48,48"/>
<end name="bad request" g="427,197,48,48"/>
<end name="server error" g="427,277,48,48"/>
</process>
測試:
//發起流程執行個體
ProcessInstance processInstance = executionService
.startProcessInstanceByKey("concurrency");
String executionId = processInstance.getId();
//指定轉移名稱:400
processInstance = executionService.signalExecutionById(executionId,"400");
//斷言流程執行個體結束
assertTrue(processInstance.isEnded());
在實際應用中,為了表明流程執行個體的結束狀态,可以利用end活動的state屬性辨別 或者利用jBPM4提供的特殊end活動:end-cancel活動和end-error活動。
例如:
<process name="concurrency" xmlns="http://jbpm.org/4.4/jpdl">
<start g="57,199,48,48">
<transition to="get code"/>
</start>
<!-- 3條流出轉移指向不同狀态的end活動 -->
<state name="get code" g="213,196,92,52">
<transition name="200" to="ok" g="258,135:-33,-5"/>
<transition name="400" to="bad request" g="-45,-25"/>
<transition name="500" to="server error" g="261,317:-36,-48"/>
</state>
<!-- 此end活動設定流程的狀态為completed -->
<end name="ok" g="423,112,48,48"/>
<!-- 此end活動設定流程的狀态為cancel -->
<end-cancel name="bad request" g="423,199,48,48"/>
<!-- 此end活動設定流程的狀态為error -->
<end-error name="server error" g="427,295,48,48"/>
</process>
單元測試:測200的轉移執行個體
//發起流程執行個體
ProcessInstance processInstance = executionService
.startProcessInstanceByKey("concurrency");
String executionId = processInstance.getId();
//指定轉移名稱:400
processInstance = executionService.signalExecutionById(executionId,"200");
//斷言流程執行個體的狀态與預期符合,state屬性值為completed
assertEquals("completed", processInstance.getState());
assertTrue(processInstance.isEnded());
6.task(人工任務活動)
用來處理涉及人機互動的活動。
1>關于任務的配置設定者
assignee屬性将一個任務配置設定給指定的使用者。
<process name="concurrency" xmlns="http://jbpm.org/4.4/jpdl">
<start name="start" g="111,196,48,48">
<transition to="review"/>
</start>
<!-- EL表達式"#{order.owner}"的值在這裡表示配置設定者ID -->
<task name="review" assignee="#{order.owner}" g="264,190,92,52">
<transition to="wait"/>
</task>
<state name="wait" g="431,189,92,52"/>
</process>
注意:assignee屬性引用了一個使用者,即負責完成任務的人;assignee屬性預設會作為EL表達式來執行,#{order.owner}意味着使用order這個名稱在任務對應的流程變量中查找一個對象,然後通過order對象的getOwner方法獲得使用者ID。
public class Order implements Serializable{
//owner成員域儲存使用者的ID
String owner;
public Order(String owner){
this.owner = owner;
}
public String getOwner() {
return owner;
}
public void setOwner(String owner) {
this.owner = owner;
}
}
基于此定義進行測試:
Map<String, Object> vars = new HashMap<>();
vars.put("order", new Order("alex"));
//發起流程執行個體
ProcessInstance processInstance = executionService
.startProcessInstanceByKey("concurrency",vars);
//使用者alex可通過findPersonalTasks獲得
List<Task> taskList = taskService.findPersonalTasks("alex");
2.關于任務的候選者
jBPM支援将任務配置設定給一組候選使用者,組内的一個使用者可以接受這個任務并完成。
<process name="concurrency" xmlns="http://jbpm.org/4.4/jpdl">
<start name="start" g="111,196,48,48">
<transition to="review"/>
</start>
<!-- 這裡用字元串引用了一個使用者組:sales-dept -->
<task name="review" candidate-groups="sales-dept" g="264,190,92,52">
<transition to="wait"/>
</task>
<state name="wait" g="431,189,92,52"/>
</process>
流程執行個體發起後,任務review會被建立,但是不會顯示在任何人的個人任務清單中,因為還沒有建立sales-dept組。可以通過taskService.findGroupTasks來擷取。
//首先建立sales-dept組
identityService.createGroup("sales-dept");
//建立使用者alex
identityService.createUser("alex","alex", "Alex", "Miller");
//将alex加入sales-dept組
identityService.createMembership("alex", "sales-dept");
//建立使用者joes
identityService.createUser("joes", "joes", "Joe","Smoe");
//将joes加入sales-dept組
identityService.createMembership("joes", "sales-dept");
此任務将會有2個後選擇——alex和joes,候選者在處理任務之前,必須先接受任務,這時兩個候選者将同時看到任務。
//接受任務
taskService.taskTask(task.getId(),"alex");
此時alex接受了任務後,就會由任務的候選者變為任務的配置設定者,同時此任務會從所有候選者的任務清單中消失,它會出現在alex的已配置設定任務清單中。
3>.關于任務配置設定處理器
任務配置設定處理器需要實作AssignmentHandler接口
任務配置設定處理器作為任務活動的一個子元素,名稱為assignment-handler。它指向使用者代碼實作的AssignmentHandler接口。
<process name="concurrency" xmlns="http://jbpm.org/4.4/jpdl">
<start name="start" g="111,196,48,48">
<transition to="review"/>
</start>
<task name="review" g="264,190,92,52">
<!-- AssignTask作為任務配置設定處理器的實作 -->
<assignment-handler class="test.AssignTask">
<!-- assignee注入值 -->
<field name="assignee">
<string value="alex"/>
</field>
</assignment-handler>
<transition to="wait"/>
</task>
<state name="wait" g="431,189,92,52"/>
</process>
對應的任務配置設定處理器:
public class AssignTask implements AssignmentHandler {
String assignee;
@Override
public void assign(Assignable assignable, OpenExecution execution)
throws Exception {
//設定任務的配置設定者
assignable.setAssignee(assignee);
}
}
//發起流程執行個體
ProcessInstance processInstance = executionService
.startProcessInstanceByKey("concurrency");
//alex是通過定義注入的任務配置設定者
List<Task> taskList = taskService.findPersonalTasks("alex");
//斷言alex有一個任務
assertEquals(1, taskList.size());
Task task = taskList.get(0);
//斷言任務名稱
assertEquals("review", task.getName());
//斷言任務的配置設定者
assertEquals("alex", task.getAssignee());
此流程運作後到任務活動review,當review任務被建立時,AssignTask任務配置設定處理器被調用,此時已設定alex使用者為此任務的配置設定者,是以alex将在他的個人任務清單中找到這個任務。
4>.關于任務泳道
在實際業務中,流程定義中的多個任務需要被配置設定或候選給同一個群使用者,這個時候可以将"同一群使用者"定義為"一個泳道"。
在此圖中,有三個泳道——申請人、主管、财務部。
對應的jPDL檔案如下:
<process name="concurrency" xmlns="http://jbpm.org/4.4/jpdl">
<!-- 定義泳道 -->
<swimlane name="sales" candidate-groups="sales-dept"/>
<start g="111,196,48,48" name="start1">
<transition to="order data"/>
</start>
<!-- 配置設定工作 -->
<task g="264,190,92,52" name="order data" swimlane="sales">
<transition to="quote"/>
</task>
<task name="quote" swimlane="sales"/>
</process>
在xml中的泳道"sales"引用了一個使用者組sales-dept。在流程運作前這個使用者組就得需要被建立出來
identityService.createGroup("sales-dept");
//建立使用者alex并加入sales-dept組
identityService.createUser("alex", "alex", "Alex","Miller");
identityService.createMembership("alex","sales-dept");
在發起流程後,alex将成為order data的唯一候選者(因為組裡隻有他一個使用者)
//接受任務
taskService.takeTask(taskId, "alex");
接受任務後alex将成為任務的配置設定者,alex可以通過completeTask API完成任務
//完成任務
taskService.completeTask(taskId);
完成任務後,流程執行個體會流轉到下一個任務"quote",這個任務也引用了泳道sales,是以,任務會直接配置設定給alex。
驗證代碼如下:
List<Task> taskList = taskService.findPersonalTasks("alex");
//斷言alex直接拿到了任務
assertEquals(1, taskList.size());
Task task = taskList.get(0);
//斷言是否為預期的任務和配置設定者
assertEquals("quote", task.getName());
assertEquals("alex", task.getAssignee());
5>.關于任務變量
任務可以讀取、更新流程變量還可以定義任務自由的變量,主要作用是作為任務表單的資料容器——任務表單負責展現來自任務和流程的變量資料。
6>.關于任務提醒郵件
jBPM4支援使用電子郵件進行任務提醒,郵件裡的内容是根據一個模闆生成出來的,預設使用jBPM内置的,可以在process-engine-context指定自定義的模闆。
7.sub-process(子流程活動)