說說如何使用 jBPM4 的 Service API 來控制流程

1 什么是流程定義、流程實(shí)例與流程執(zhí)行?

流程定義是對業(yè)務(wù)過程步驟的描述。在 jbpm4 中,它表現(xiàn)為若干 "活動(dòng)" 節(jié)點(diǎn)通過 “轉(zhuǎn)移” 路徑串聯(lián)起來。比如下面定義的這個(gè)信貸流程:

信貸流程

流程實(shí)例表示的是流程定義在運(yùn)行時(shí)特有的執(zhí)行例程。比如,上周你提出了貸款買房申請,那么這個(gè)信貸流程定義就被實(shí)例化咯。

一個(gè)流程實(shí)例在其生命周期中最典型的特征就是:具有指向當(dāng)前執(zhí)行活動(dòng)的指針,在 jbpm4 中叫做 executions。比如下面這個(gè)流程實(shí)例正執(zhí)行到 “歸檔” 活動(dòng):

執(zhí)行中的流程實(shí)例

流程實(shí)例支持并行執(zhí)行,所以在同一個(gè)流程實(shí)例中的執(zhí)行數(shù)量可能有多個(gè),修改上面的貸款流程定義,讓匯款和存檔并行執(zhí)行,那么這里的主流程實(shí)例就可能包含兩個(gè)用來跟蹤狀態(tài)的子執(zhí)行實(shí)例(execution)。

并行執(zhí)行的流程實(shí)例

流程實(shí)例可以理解為一顆執(zhí)行樹,當(dāng)一個(gè)流程實(shí)例啟動(dòng)時(shí)最初的執(zhí)行處于這個(gè)執(zhí)行樹的根節(jié)點(diǎn)位置,之后根據(jù)定義的需要再產(chǎn)生子執(zhí)行實(shí)例,即樹枝。

2 流程引擎 API

Jbpm4 所有 Service API 都來源于流程引擎對象 org.jbpm.api.ProcessEngine,即所有的 sevice API 都可以從
ProcessEngine 中獲得。ProcessEngine 是由工作流引擎根據(jù)配置生成的。

processEngine 是線程安全的,所以可以保存在靜態(tài)變量中。實(shí)踐中,所有的線程和請求都可以使用同一個(gè) processEngine 對象。這樣獲取 processEngine 對象:

ProcessEngine processEngine = configuration.buildProcessEngine();

這里根據(jù) classpath 根目錄下的默認(rèn)配置文件(jbpm.cfg.xml)創(chuàng)建了ProcessEngine 對象。

如果要指定其他位置的 jbpm 配置文件,可以使用 Configuration.setResource 方法:

ProcessEngine processEngine =new Configuration.setResource("other-jbpm-confguration-file.xml").buildProcessEngine();

當(dāng)然也可以從其他的 setXxx() 方法中(比如 InputStream、XML 字符串等)獲取 jBPM 配置內(nèi)容。

可以通過 ProcessEngine 實(shí)例得到 jBPM4 封裝的 6 個(gè) Service API:

RepositoryService repositoryService = processEngine.getRepositoryService();
ExecutionService executionService = processEngine.getExecutionService();
TaskService taskService = processEngine.getTaskService();
HistoryService historyService = processEngine.getHistoryService();
ManagementService managementService = processEngine.getManagementService();
IdentityService identityService=processEngine.getIdentityService();

這些 Service API 都位于 org.jbpm.api 包中:

  • RepositoryService - 流程資源服務(wù)接口。提供對流程定義的部署、查詢和刪除操作。
  • ExecutionService - 流程執(zhí)行服務(wù)接口。提供啟動(dòng)流程實(shí)例、執(zhí)行對象的推進(jìn)和設(shè)置流程變量等操作。
  • TaskService - 流程任務(wù)服務(wù)接口。提供對任務(wù)的創(chuàng)建、提交、查詢、保存和刪除等操作。
  • ManagementService :流程管理控制服務(wù)接口。提供對異步工作(Job)的執(zhí)行和查詢操作。
  • HistoryService :流程歷史服務(wù)接口。提供對流程歷史庫(即已完成的流程實(shí)例歸檔數(shù)據(jù))中歷史流程實(shí)例、歷史活動(dòng)實(shí)例等數(shù)據(jù)的查詢操作。還提供諸如某個(gè)流程定義中所有活動(dòng)的平均持續(xù)時(shí)間、某個(gè)流程定義中某次轉(zhuǎn)移的經(jīng)過次數(shù)等數(shù)據(jù)分析服務(wù)。
  • IdentityService:身份認(rèn)證服務(wù)接口。提供與流程用戶、用戶組以及組內(nèi)成員關(guān)系的相關(guān)服務(wù)。

這些 Service API 都繼承自 AbstractServiceImpl 類,這個(gè)類依賴于 CommandService。

AbstractServiceImpl 源代碼:

public class AbstractServiceImpl {
  
  protected CommandService commandService;

  public CommandService getCommandService() {
    return commandService;
  }

  public void setCommandService(CommandService commandService) {
    this.commandService = commandService;
  }
}

CommandService 是 Command 模式的服務(wù)接口,它會(huì)將客戶端的請求全部封裝在一個(gè)調(diào)用接口中,然后由這個(gè)接口去調(diào)用org.jbpm.api.cmd.Command 接口的眾多實(shí)現(xiàn)。

jbpm4 Sevice API 的實(shí)現(xiàn)廣泛地采用了 Command 設(shè)計(jì)模式。


Command 模式的目的即在不同的時(shí)刻指定、排列和執(zhí)行請求。一個(gè)Command 對象可以有一個(gè)與初始化請求無關(guān)的生存期。如果一個(gè)請求的接受者可用一種與地址空間無關(guān)的方式表達(dá),那么就可以將負(fù)責(zé)該請求的命令對象傳遞給另一個(gè)不同的進(jìn)程并在那里實(shí)現(xiàn)該請求。

Command 模式的優(yōu)勢在于:

  • 支持取消操作。Command 的 Execute 操作可以在實(shí)施操作前將狀態(tài)存儲起來,在取消操作時(shí)使用這個(gè)狀態(tài)來消除這個(gè)操作的影響。執(zhí)行的命令被存儲在一個(gè)歷史列表中。這樣就可以通過向后或向前遍歷這個(gè)列表來實(shí)現(xiàn)不限次數(shù)的 “取消” 與 “重做” 操作。
  • 支持修改日志。在 Command 接口中添加裝載與存儲操作,可以動(dòng)態(tài)保持一個(gè)一致的修改日志。
  • 用構(gòu)建在原語操作的的高層操作中構(gòu)建一個(gè)系統(tǒng)。特別是支持事務(wù)的信息系統(tǒng)中很常見。一個(gè)事務(wù)封裝了對數(shù)據(jù)的一組變動(dòng)。Command 有一個(gè)公共的接口,使得可以使用同一種方法來調(diào)用所有的事務(wù)。

3 部署流程

RepositoryService 提供了發(fā)布資源的所有接口,我們可以使用它來部署 classpath 中的一個(gè)流程定義資源:

repositoryService.createDeployment().addResourceFromClasspath(filePath).addResourceFromClasspath(pngFilePath).deploy();

其中的 filePath,表示流程定義文件所在路徑;pngFilePath 表示 png 文件所在路徑(用于應(yīng)用中展示流程圖)。

也通過 addResourceFromXXX 的系列方法,從文件、Web URL、字符串、輸入流或 Zip 流中獲取流程定義文件。

部署的資源內(nèi)容都是字節(jié)數(shù)組的形式保存。jPDL 流程定義文件以擴(kuò)展名 .jpdl.xml 被識別。其他資源文件包括任務(wù)表單、Java 類和腳本等。如果不僅要部署 .jpdl.xml 流程定義文件,而且要部署一系列的流程定義資源,則可以以流程定義歸檔的方式部署,流程引擎會(huì)自動(dòng)識別出歸檔中擴(kuò)展名為 .jpdl.xml 文件為流程定義文件。

部署時(shí),流程引擎會(huì)分配一個(gè) ID 給流程定義。它的格式是 {key}-{version} ,即流程的鍵與流程版本號之間通過連字符拼接起來。

如果流程定義沒有指定 key,那么引擎會(huì)在流程名稱的基礎(chǔ)上生成。生成的 key 會(huì)把所有不是字母或數(shù)字的字符替換成下劃線。

一個(gè)流程名稱只能關(guān)聯(lián)一個(gè) key。

如果沒有為流程定義文件指定版本號,那么引擎會(huì)自動(dòng)為其分配一個(gè)版本號(版本號為 1)。如果要部署的流程定義的 Key 已存在,那么版本號會(huì)自動(dòng)遞增。

舉例說明,下面的這個(gè)流程定義只設(shè)置了流程名稱:

<process name="workflow">
...
</process>

那么它部署后的屬性是這樣的:

屬性名稱 屬性值 來源
name workflow 流程定義文件
key workflow 引擎生成
version 1 引擎生成
id workflow-1 引擎生成

我們可以通過制定流程定義的 key 來獲得更簡潔的 id:

<process name="workflow" key="wf">
...
</process>

它部署后的屬性是這樣的:

屬性名稱 屬性值 來源
name workflow 流程定義文件
key wf 流程定義文件
version 1 引擎生成
id wf-1 引擎生成

實(shí)踐中,建議主動(dòng)設(shè)置流程定義文件的版本號,這樣方便管理與維護(hù)哦O(∩_∩)O~

4 刪除已部署的流程

可以從物理上刪除已部署的流程,即會(huì)在數(shù)據(jù)庫中徹底銷毀這條流程定義的記錄:

repositoryService.deleteDeploymentCascade(deploymentId);

如果要?jiǎng)h除的流程定義有還未完成的流程實(shí)例,那么執(zhí)行 deleteDeploymentCascade() 方法會(huì)拋出異常。

可以使用 repositoryService 的 deleteDeploymentCascade 方法級聯(lián)刪除一個(gè)已發(fā)布的流程定義以及其所產(chǎn)生的流程實(shí)例。

5 發(fā)起新的流程實(shí)例

5.1 普通方法

ProcessInstance processInstance=executionService.startProcessInstanceByKey("wf");

startProcessInstanceByKey 方法會(huì)去查找 key 為 wf 的最新版本的流程定義,然后根據(jù)最新版本的流程定義來啟動(dòng)流程實(shí)例。當(dāng)這個(gè)流程定義部署了一個(gè)新的版本后,startProcessInstanceByKey 方法會(huì)自動(dòng)切換到最新版本并已部署的流程定義對象。

如果想根據(jù)特定的流程定義版本來發(fā)起流程實(shí)例,那么可以通過流程定義的 id 來啟動(dòng)流程實(shí)例:

ProcessInstance processInstance=executionService.startProcessInstanceById("wf-1");

5.2 指定業(yè)務(wù)鍵來發(fā)起流程實(shí)例

一般情況下,一個(gè)流程實(shí)例會(huì)與一個(gè)獨(dú)特的業(yè)務(wù)實(shí)例關(guān)聯(lián)起來,比如一個(gè)工單流程實(shí)例必然會(huì)與一個(gè)工單號相關(guān)聯(lián),以便滿足業(yè)務(wù)上的查詢操作。這時(shí)我們就會(huì)為每一個(gè)新啟動(dòng)的流程實(shí)例分配一個(gè)業(yè)務(wù)鍵(processInstanceKey)。

業(yè)務(wù)鍵是用戶執(zhí)行流程時(shí)根據(jù)實(shí)際業(yè)務(wù)情況定義的。一個(gè)業(yè)務(wù)鍵必須在流程定義所有的版本的流程實(shí)例范圍內(nèi)都是唯一的。

這樣指定業(yè)務(wù)鍵來發(fā)起流程實(shí)例:

ProcessInstance processInstance=executionService.startProcessInstanceByKey("wf",“00001”);

這里的 00001 就是業(yè)務(wù)鍵。

業(yè)務(wù)鍵會(huì)被用來創(chuàng)建流程實(shí)例的 ID,格式為 {processDefnintionKey}.{processInstanceKey}。比如上面的代碼會(huì)創(chuàng)建一個(gè) ID 為 "wf.00001" 的流程實(shí)例。

如果沒有提供業(yè)務(wù)鍵,那么數(shù)據(jù)庫就會(huì)把流程定義的主鍵作為 Key。


最佳實(shí)踐:最好指定一個(gè)業(yè)務(wù)鍵來發(fā)起流程實(shí)例。這樣做的好處是可以根據(jù)業(yè)務(wù)來搜索相應(yīng)的流程實(shí)例(比如流轉(zhuǎn)日志等等常見的業(yè)務(wù)需求)。


5.3 指定變量發(fā)起流程實(shí)例

有時(shí)候需要在啟動(dòng)流程實(shí)例時(shí),傳入一些初始化參數(shù)。那么我們可以把這些參數(shù)放在流程變量中,然后在發(fā)起流程時(shí)傳入流程變量對象(Map<String,Object>)。

//創(chuàng)建流程變量
Map variables=new HashMap();
variables.put("name", "deniro");

//指定流程變量發(fā)起流程實(shí)例
ProcessInstance processInstance=executionService.startProcessInstanceByKey("wf",variables);

6 喚醒一個(gè)等待狀態(tài)的執(zhí)行對象

當(dāng)流程執(zhí)行對象進(jìn)入 state 類型的活動(dòng)時(shí),執(zhí)行對象會(huì)在到達(dá) state 活動(dòng)時(shí)進(jìn)入等待狀態(tài)(wait state),這是一個(gè)重要概念,task 等活動(dòng)也會(huì)陷入等待狀態(tài)(等待人工輸入響應(yīng)),直到觸發(fā)信號 (signal) 出現(xiàn),才會(huì)流轉(zhuǎn)到下一個(gè)活動(dòng)。ExecutionService 的 signalExecution* 方法可以用來發(fā)出signal 這個(gè)方法(執(zhí)行對象作為參數(shù))。

大多數(shù)情況下,到達(dá) state 活動(dòng)的執(zhí)行對象是流程實(shí)例本身。但在定時(shí)器異步和并發(fā)的情況下,流程實(shí)例會(huì)停留在根的執(zhí)行對象上,這時(shí)使用 signalExecution* 方法時(shí)就要確保作用在了正確的流程執(zhí)行對象上咯。

為了正確地獲取執(zhí)行對象,較好的實(shí)踐是為 state 活動(dòng)分配一個(gè)事件監(jiān)聽器,定義如下:

<state name="wait">
  <on event="start">
    <event-listener class="xxx.xxx.StartWork"/>
  </on>
</state>

監(jiān)聽器 StartWork 中,可以執(zhí)行那些需要在 state 活動(dòng)中做的工作??梢栽谶@個(gè)事件監(jiān)聽器中通過 execution.getId(); 獲得正確的執(zhí)行 id,在 state活動(dòng)的工作完成后,用它來發(fā)出 signal 信號離開該活動(dòng)。

executionService.signalExecutionById(executionId);

還有一種不推薦的方法來獲得執(zhí)行對象的 ID。當(dāng)流程執(zhí)行對象到達(dá) state 活動(dòng)時(shí)并且知道這個(gè)活動(dòng)的名稱,那么可以這樣做:

//發(fā)起流程實(shí)例
ProcessInstance processInstance=executionService.startProcessInstanceById(processDefinitionId);
//或
//ProcessInstance processInstance=executionService.signalExecutionById(executionId);

//假設(shè)知道當(dāng)前流程實(shí)例在 "external work" 的活動(dòng)中等待
Execution execution=processInstance.findActiveExecutionIn("external work");
//獲取執(zhí)行對象 ID
String executionId=execution.getId();

子所以不推薦這個(gè)方法,是因?yàn)檫@種方式使得流程的客戶端實(shí)現(xiàn)與業(yè)務(wù)邏輯綁定的較緊密,所以如果不是某些特殊的業(yè)務(wù)場景需要,不建議采用。


7 任務(wù)服務(wù) API

TaskService 的主要目的是提供對任務(wù)列表的訪問操作,這里的任務(wù)是的指 Jbpm4 task 活動(dòng)產(chǎn)生的人機(jī)交互業(yè)務(wù)。

這樣獲取 ID 為 deniro 的用戶任務(wù)列表(由用戶 deniro 來辦理的任務(wù)):

List<Task> taskList=taskService.findPersonalTasks("deniro");

一般來說,任務(wù)會(huì)有一個(gè)表單(顯示在用戶頁面中)。這個(gè)表單會(huì)通過任務(wù)變量來讀寫與任務(wù)有關(guān)的流程數(shù)據(jù)。

long taskId=task.getId();
Set<String> variableNames=taskService.getVariableNames(taskId);

//讀取任務(wù)變量
HashMap<String,Object> variables=taskService.getVariables(taskId,variableNames);
//或自行創(chuàng)建 variables=new HashMap<String,Object>();

//設(shè)置 "鍵-值" 形式的任務(wù)變量
variables.put("name", "deniro");

//將變量存入任務(wù)
taskService.setVariables(taskId, variables);

TaskService 提供了四種方式來完成任務(wù):

//根據(jù)指定的任務(wù) ID 完成任務(wù)
taskService.completeTask(taskId);

//根據(jù)指定的任務(wù) ID 完成任務(wù),同時(shí)設(shè)入變量,完成任務(wù)
taskService.completeTask(taskId,variables);

//指定 outcome,即下一步的轉(zhuǎn)移路徑,完成任務(wù)
taskService.completeTask(taskId,outcome);

//指定下一步的轉(zhuǎn)移路徑,同時(shí)設(shè)入變量,完成任務(wù)
taskService.completeTask(taskId,outcome,variableNames);

Outcome 這個(gè)參數(shù)可以用來決定任務(wù)完成后,流程流向哪一個(gè)流出 “轉(zhuǎn)移” 路徑。流程的流轉(zhuǎn)遵循以下規(guī)則 ——

  1. 如果任務(wù)有一個(gè)沒有名稱的流出轉(zhuǎn)移:
  • taskService.getOutcomes(taskId) 返回包含一個(gè) null 值的集合。
  • taskService.completeTask(taskId) 會(huì)經(jīng)過這個(gè)流出轉(zhuǎn)移。
  • taskService.completeTask(taskId,null) 會(huì)經(jīng)過這個(gè)流出轉(zhuǎn)移。
  • taskService.completeTask(taskId,"anyvalue")會(huì)拋出一個(gè)異常。
  1. 如果任務(wù)有一個(gè)已命名為 “myName” 的流出轉(zhuǎn)移:
  • taskService.getOutcomes(taskId) 返回包含這個(gè)流出轉(zhuǎn)移的名稱集合。
  • taskService.completeTask(taskId) 會(huì)經(jīng)過這個(gè)流出轉(zhuǎn)移。
  • taskService.completeTask(taskId,null)會(huì)拋出一個(gè)異常。因?yàn)榇巳蝿?wù)沒有無名稱的流出轉(zhuǎn)移。
  • taskService.completeTask(taskId,"myName") 會(huì)經(jīng)過這個(gè)流出轉(zhuǎn)移。
  • taskService.completeTask(taskId,"other") 會(huì)拋出一個(gè)異常。
  1. 如果任務(wù)擁有多個(gè)流出轉(zhuǎn)移,而其中一個(gè)沒有名稱,其他的都有名稱(其中一個(gè)叫 myName):
  • taskService.getOutcomes(taskId) 返回包含一個(gè) null 值和其他流出轉(zhuǎn)移的名稱集合。
  • taskService.completeTask(taskId) 會(huì)經(jīng)過沒有名稱的流出轉(zhuǎn)移。
  • taskService.completeTask(taskId,null) 會(huì)經(jīng)過沒有名稱的流出轉(zhuǎn)移。
  • taskService.completeTask(taskId,"myName")會(huì)經(jīng)過名稱為 myName 的流出轉(zhuǎn)移。

4.如果任務(wù)擁有多個(gè)流出轉(zhuǎn)移,且每個(gè)流出轉(zhuǎn)移都擁有唯一的名稱(其中一個(gè)叫 myName):

  • taskService.getOutcomes(taskId) 包含所有流出轉(zhuǎn)移名稱的集合。
  • taskService.completeTask(taskId) 會(huì)拋出一個(gè)異常,因?yàn)闆]有無名稱的流出轉(zhuǎn)移。
  • taskService.completeTask(taskId,null)會(huì)拋出一個(gè)異常,因?yàn)闆]有無名稱的流出轉(zhuǎn)移。
  • taskService.completeTask(taskId,"myName")會(huì)經(jīng)過名稱為 myName 的流出轉(zhuǎn)移。
  • taskService.completeTask(taskId,"other") 會(huì)拋出一個(gè)異常。

一個(gè)任務(wù)可以擁有多個(gè)候選人,候選人可以是單個(gè)用戶也可以是用戶組。用戶可以接受候選人是自己的任務(wù),接受(或分配)任務(wù)指的是用戶被流程引擎設(shè)置為任務(wù)的辦理者。分配任務(wù)是個(gè) ”排他“ 操作,因此在任務(wù)被分配之后,其他的用戶就不能被分配并辦理此任務(wù)咯。

一般情況下,除非用戶被分配到這個(gè)任務(wù)上,否則不能辦理這個(gè)任務(wù)。但中國特色的 “代理人” 機(jī)制是一個(gè)例外,這里先留個(gè)懸念,我們以后會(huì)說到。O(∩_∩)O~

用戶接受任務(wù)后,一般需要客戶端應(yīng)用程序界面(網(wǎng)頁)顯示任務(wù)表單,并引導(dǎo)用戶完成任務(wù)。對于有候選人、但還沒有被分配的任務(wù),唯一應(yīng)該暴露給用戶的操作是 ”接受任務(wù)“。

8 歷史服務(wù) API

在流程實(shí)例執(zhí)行的過程中,會(huì)不斷觸發(fā)事件,通過這些事件,已完成流程實(shí)例的歷史信息會(huì)被記錄到流程歷史數(shù)據(jù)表中。而 HistoryService API 提供了對這些歷史信息的訪問服務(wù)。

可以這樣查找特定流程定義的所有歷史流程實(shí)例:

List<HistoryProcessInstance> historyProcessInstances=historyService.createHistoryDetailQuery()
//查詢 Id 為 “wf-1” 的流程定義
.processDefinitionId("“wf-1")
//返回的結(jié)果集按開始時(shí)間正序排列
.oderAsc(HistoryProcessInstanceQuery.PROPERTY_STARTTIME)
.list();

通過 HistoryActivtyInstance,可以查詢歷史的活動(dòng)實(shí)例:

List<HistoryActivityInstance> historyActInsts=historyService
.createHistoryActivityInstanceQuery()
//查詢 Id 為 “wf-1” 的流程定義
.processDefinitionId("“wf-1")
//名稱為 “審核” 的活動(dòng)實(shí)例
.activityName("審核")
.list();

HistoryService 還可以對流程的歷史進(jìn)行分析:

  • avgDurationPerActivity - 獲取指定流程定義中每個(gè)活動(dòng)的平均執(zhí)行時(shí)間。
  • choiceDistribution - 獲取指定活動(dòng)定義中每個(gè)轉(zhuǎn)移路徑的經(jīng)過次數(shù)。

9 管理服務(wù) API

ManagementService 即管理服務(wù),它通常用來管理 Job(異步的工作)。

ManagementService 提供以下兩個(gè)方法:

//執(zhí)行指定 ID 的 Job
Void execteJob(String jobId);

//獲取 Job 查詢接口
JobQuery createJobQurey();

JobQuery 提供的功能很多:

/** only select messages (查詢所有消息型的 Job)*/
  JobQuery messages();
  
  /** only select timers (查詢所有定時(shí)器的 Job)*/
  JobQuery timers();
  
  /** only select jobs related to the given process instance (查詢屬于指定流程實(shí)例的 Job)*/ 
  JobQuery processInstanceId(String processInstanceId);
  
  /** only select jobs that were rolled back due to an exception  (查詢由于異?;貪L產(chǎn)生的 Job)*/ 
  JobQuery exception(boolean hasException);

  /** order ascending for property {@link #PROPERTY_STATE} 
   * or {@link #PROPERTY_DUEDATE}(查詢結(jié)果根據(jù)指定屬性正序排列) */
  JobQuery orderAsc(String property);

  /** order descending for property {@link #PROPERTY_STATE} 
   * or {@link #PROPERTY_DUEDATE} (查詢結(jié)果根據(jù)指定屬性逆序排列)*/
  JobQuery orderDesc(String property);

  /** only select a specific page(查詢結(jié)果分頁) */ 
  JobQuery page(int firstResult, int maxResults);

  /** execute the query and get the result list (執(zhí)行查詢,返回 Job 列表)*/ 
  List<Job> list();

  /** execute the query and get the unique result (執(zhí)行查詢,返回單個(gè) Job ) */ 
  Job uniqueResult();
  
  /** execute a count(*) query and returns number of results (執(zhí)行查詢,返回結(jié)果集的數(shù)量 ) */ 
  long count();

10 查詢服務(wù) API

查詢服務(wù)的 API 是基于主要的 jBPM 概念實(shí)體上創(chuàng)建查詢對象來實(shí)現(xiàn)的,這些實(shí)體包括流程實(shí)例、任務(wù)、流程歷史等概念。

查詢流程實(shí)例:

List<ProcessInstance> results = executionService
//獲取流程實(shí)例查詢對象
.createProcessInstanceQuery()
//指定流程定義 ID
.processDefinitionId("process_defintion_id")
//設(shè)置 “為掛起” 為條件
.notSuspended()
//分頁
.page(0, 100)
//獲得結(jié)果列表
.list();

上面代碼會(huì)返回指定流程定義中所有未掛起的流程實(shí)例,結(jié)果集支持分頁,獲取前 100 條記錄。

任務(wù)的查詢也可以使用類似的查詢對象:

List<Task> myTasks=taskService
//獲取任務(wù)查詢對象
.createTaskQuery()
//指定流程實(shí)例 ID
.processInstanceId(piId)
//分配給 deniro 的任務(wù)
.assignee("deniro ")
//分頁
.page(100, 200)
//根據(jù)日期逆向排序
.orderDesc(TaskQuery.PROPERTY_DUEDATE)
//獲得結(jié)果列表
.list();

這個(gè)查詢根據(jù)指定的流程實(shí)例,獲取分配給 deniro 的所有任務(wù)信息(支持分頁,數(shù)據(jù)取自第 100 條開始的 200 條記錄,根據(jù)日期逆向排序)。

幾乎所有的服務(wù)都擁有這樣的一個(gè)統(tǒng)一查詢對象。比如查詢 Job 可以通過 ManagementService 來創(chuàng)建 JobQuery 對象。

11 范例:使用 Service API 實(shí)現(xiàn)流程實(shí)例的流轉(zhuǎn)

這一節(jié)將演示使用 Service API 來發(fā)起、執(zhí)行、完成整個(gè)流程實(shí)例以及查詢該流程實(shí)例的歷史信息等功能。

假設(shè),有這樣的一個(gè)流程定義:

流程定義

對應(yīng)的 jPDL 如下:

<?xml version="1.0" encoding="UTF-8"?>

<process name="process" xmlns="http://jbpm.org/4.4/jpdl">
   <start g="502,41,48,48" name="start1">
      <transition g="-56,-22" name="to state1" to="state1"/>
   </start>
   <state g="482,113,92,52" name="state1">
      <transition g="-52,-22" name="to task1" to="task1"/>
   </state>
   <end g="504,313,48,48" name="end1"/>
   <task assignee="Alex" g="481,211,92,52" name="task1">
      <transition g="-50,-22" name="to end1" to="end1"/>
   </task>
</process>

這里的 state1 是需要等待的活動(dòng),它需要一個(gè)外部執(zhí)行信號才能流轉(zhuǎn)通過;task1 也是需要等待的活動(dòng),它被分配給用戶 Alex 辦理,Alex 辦理后才能流轉(zhuǎn)通過。

下面的代碼是基于 JbpmTestCase 的單元測試。這里列出了執(zhí)行流程定義部署的 SetUp 方法與刪除流程定義的 tearDown 方法:

public class ProcessTest extends JbpmTestCase {

    /**
     * 流程定義的部署 ID
     */
    String deploymentId;

    /**
     * 初始化方法中執(zhí)行流程部署工作
     */
    @Override
    protected void setUp() throws Exception {
        super.setUp();

        //從 classpath 中部署流程定義
        deploymentId = repositoryService.createDeployment().addResourceFromClasspath("net/deniro/jbpm/test/process.jpdl.xml").deploy();

        //可以多次調(diào)用 addResourceFromClasspath 方法,把多個(gè)資源都部署到數(shù)據(jù)庫中


    }

    /**
     *
     */
    @Override
    protected void tearDown() throws Exception {
        //物理清除 deploymentId 對應(yīng)的流程定義及其所有相關(guān)資源
        repositoryService.deleteDeploymentCascade(deploymentId);
        super.tearDown();
    }

    public void test() {
        //單元測試代碼

        //根據(jù)流程定義名稱,發(fā)起流程實(shí)例
        ProcessInstance processInstance = executionService.startProcessInstanceByKey("process");

        //獲取流程實(shí)例 ID
        String pid = processInstance.getId();

        //獲取當(dāng)前活動(dòng)的執(zhí)行對象
        Execution executionInState = processInstance.findActiveExecutionIn("state1");
        assertNotNull(executionInState);

        //發(fā)出執(zhí)行信號,結(jié)束當(dāng)前活動(dòng),讓流程流轉(zhuǎn)到下一節(jié)點(diǎn)
        executionService.signalExecutionById(executionInState.getId());

        //從持久化層中,獲取 “最新”的流程實(shí)例對象
        processInstance = executionService.findProcessInstanceById(pid);

        //判斷當(dāng)前活動(dòng)的執(zhí)行對象
        Execution executionInTask = processInstance.findActiveExecutionIn("task1");
        assertNotNull(executionInTask);

        //獲取用戶 Alex 的任務(wù),即 task 活動(dòng)產(chǎn)生的任務(wù)
        Task task = taskService.findPersonalTasks("Alex").get(0);

        //完成任務(wù)
        taskService.completeTask(task.getId());

        //查詢歷史任務(wù)
        HistoryTask historyTask = historyService.createHistoryTaskQuery().taskId(task.getId()).uniqueResult();
        assertNotNull(historyTask);
        assertProcessInstanceEnded(pid);//斷言:流程實(shí)例已結(jié)束

        HistoryProcessInstance historyProcInst = historyService
                .createHistoryProcessInstanceQuery().processInstanceId(pid).uniqueResult();
        assertNotNull(historyProcInst);//斷言這個(gè)流程實(shí)例已成為歷史,所以可以從【歷史流程實(shí)例查詢對象】中得到它


    }

}

使用 Service API 可以讓一個(gè)流程實(shí)例走完它的整個(gè)生命周期,所以熟悉并掌握這些 API 是開發(fā) jBPM 客戶端應(yīng)用的基礎(chǔ)哦O(∩_∩)O~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,659評論 19 139
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,228評論 25 708
  • 我記得木心先生有過一句俳比: 年輕人充滿希望的清瘦。 ...
    措像閱讀 1,015評論 0 0
  • 你說我們沒有在一起,但是不代表我不愛你,可是我們沒有在一起,就算再喜歡有什么用呢,你拿什么愛我,你最終還不是娶了別人。
    七分閱讀 202評論 0 2
  • 基本用法:HTML: 參數(shù): Tips: 多個(gè)收件人郵件地址之間用;分隔。 參考資料:http://www.rap...
    Ruby君閱讀 303評論 0 0

友情鏈接更多精彩內(nèi)容