說說如何使用 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  (查詢由于異撤古樱回滾產(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)系作者
  • 序言:七十年代末萤厅,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子靴迫,更是在濱河造成了極大的恐慌惕味,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,820評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件玉锌,死亡現(xiàn)場離奇詭異名挥,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)主守,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,648評論 3 399
  • 文/潘曉璐 我一進(jìn)店門禀倔,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人丸逸,你說我怎么就攤上這事蹋艺。” “怎么了黄刚?”我有些...
    開封第一講書人閱讀 168,324評論 0 360
  • 文/不壞的土叔 我叫張陵捎谨,是天一觀的道長。 經(jīng)常有香客問我,道長涛救,這世上最難降的妖魔是什么畏邢? 我笑而不...
    開封第一講書人閱讀 59,714評論 1 297
  • 正文 為了忘掉前任,我火速辦了婚禮检吆,結(jié)果婚禮上舒萎,老公的妹妹穿的比我還像新娘。我一直安慰自己蹭沛,他們只是感情好臂寝,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,724評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著摊灭,像睡著了一般咆贬。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上帚呼,一...
    開封第一講書人閱讀 52,328評論 1 310
  • 那天掏缎,我揣著相機(jī)與錄音,去河邊找鬼煤杀。 笑死眷蜈,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的沈自。 我是一名探鬼主播酌儒,決...
    沈念sama閱讀 40,897評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼枯途!你這毒婦竟也來了今豆?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,804評論 0 276
  • 序言:老撾萬榮一對情侶失蹤柔袁,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后异逐,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體捶索,經(jīng)...
    沈念sama閱讀 46,345評論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,431評論 3 340
  • 正文 我和宋清朗相戀三年灰瞻,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了腥例。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,561評論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡酝润,死狀恐怖燎竖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情要销,我是刑警寧澤构回,帶...
    沈念sama閱讀 36,238評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響纤掸,放射性物質(zhì)發(fā)生泄漏脐供。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,928評論 3 334
  • 文/蒙蒙 一借跪、第九天 我趴在偏房一處隱蔽的房頂上張望政己。 院中可真熱鬧,春花似錦掏愁、人聲如沸歇由。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,417評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽沦泌。三九已至,卻和暖如春京腥,著一層夾襖步出監(jiān)牢的瞬間赦肃,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,528評論 1 272
  • 我被黑心中介騙來泰國打工公浪, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留他宛,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,983評論 3 376
  • 正文 我出身青樓欠气,卻偏偏與公主長得像厅各,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子预柒,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,573評論 2 359

推薦閱讀更多精彩內(nèi)容

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