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):
流程實(shí)例支持并行執(zhí)行与斤,所以在同一個(gè)流程實(shí)例中的執(zhí)行數(shù)量可能有多個(gè),修改上面的貸款流程定義荚恶,讓匯款和存檔并行執(zhí)行撩穿,那么這里的主流程實(shí)例就可能包含兩個(gè)用來跟蹤狀態(tài)的子執(zhí)行實(shí)例(execution)。
流程實(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ī)則 ——
- 如果任務(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è)異常逻悠。
- 如果任務(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è)異常饥伊。
- 如果任務(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~