天天看點

JBPM(六)——掌握JBPM流程定義語言

一、流程

    在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活動。

示例:

JBPM(六)——掌握JBPM流程定義語言

對應的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通過。

JBPM(六)——掌握JBPM流程定義語言
<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。

JBPM(六)——掌握JBPM流程定義語言

對應的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屬性來判斷流程的轉向,需要指定流轉的路徑

JBPM(六)——掌握JBPM流程定義語言
<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活動的子元素進行配置。

JBPM(六)——掌握JBPM流程定義語言

對應的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則可以使流程的并發分支聚合成一條主幹。

JBPM(六)——掌握JBPM流程定義語言

對應的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活動的流程執行個體中仍然活躍的流程活動将會被保留繼續執行。

簡單流程定義:

JBPM(六)——掌握JBPM流程定義語言
<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活動。

例如:

JBPM(六)——掌握JBPM流程定義語言
<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屬性将一個任務配置設定給指定的使用者。

JBPM(六)——掌握JBPM流程定義語言
<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支援将任務配置設定給一組候選使用者,組内的一個使用者可以接受這個任務并完成。

JBPM(六)——掌握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接口。

JBPM(六)——掌握JBPM流程定義語言
<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>.關于任務泳道

在實際業務中,流程定義中的多個任務需要被配置設定或候選給同一個群使用者,這個時候可以将"同一群使用者"定義為"一個泳道"。

JBPM(六)——掌握JBPM流程定義語言

在此圖中,有三個泳道——申請人、主管、财務部。

JBPM(六)——掌握JBPM流程定義語言

對應的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(子流程活動)