前言
最近接到上級(jí)的安排,要我為公司的某個(gè)系統(tǒng)設(shè)計(jì)一套工作流引擎。對(duì)于工作流引擎這一個(gè)主題,我查找了許多資料,在java方面用的比較多的是一個(gè)名叫Activiti的開(kāi)源工作流引擎,通過(guò)與同事們交流之后,決定了使用該開(kāi)源工作流引擎。在這段時(shí)間也是花了些時(shí)間來(lái)學(xué)習(xí)研究Activiti。網(wǎng)上的資料并不多,官方的文檔是全英文的,而且更新的貌似不是很及時(shí),所以準(zhǔn)備寫(xiě)這個(gè)系列的文檔來(lái)記錄我的學(xué)習(xí)過(guò)程。
官方地址
我的寫(xiě)作水平還有待提高,可能很多人更愿意直接去看官方的一些教程,所以我先把地址放上!
碼云鏡像-activiti-7-developers-guide
關(guān)于BPMN
BPMN(Business Process Model AndNotation)- 業(yè)務(wù)流程模型和符號(hào) 是由BPMI(BusinessProcess Management Initiative)開(kāi)發(fā)的一套標(biāo)準(zhǔn)的業(yè)務(wù)流程建模符號(hào),使用BPMN提供的符號(hào)可以創(chuàng)建業(yè)務(wù)流程。2004年5月發(fā)布了BPMN1.0規(guī)范.BPMI于2005年9月并入OMG(The Object Management Group對(duì)象管理組織)組織OMG于2011年1月發(fā)布BPMN2.0的最終版本。
Activiti 就是使用 BPMN 2.0 進(jìn)行流程建模、流程執(zhí)行管理,它包括很多的建模符號(hào)。

可以使用這些符號(hào)來(lái)繪制流程圖,例如下圖:

Activiti也是通過(guò)將這些流程圖的BPMN文件部署到數(shù)據(jù)庫(kù)中,然后啟動(dòng)相應(yīng)的流程,來(lái)完成工作流的一個(gè)映射。這些節(jié)點(diǎn)可以指定一些參數(shù)、表達(dá)式、綁定事件或者綁定解析處理類,來(lái)實(shí)現(xiàn)對(duì)每個(gè)流程節(jié)點(diǎn)的處理。
對(duì)于BPMN流程圖的繪制方法這里就不做贅述,因?yàn)槲乙膊皇橇私夂芏?,網(wǎng)上有許多非常好的文章可供參考。
Activiti 的架構(gòu)
引擎API是與Activiti交互的最常見(jiàn)方式。中心起點(diǎn)是
ProcessEngine,可以按照配置部分中所述的多種方式創(chuàng)建 。從ProcessEngine,您可以獲得包含工作流/ BPM方法的各種服務(wù)。ProcessEngine和服務(wù)對(duì)象是線程安全的。因此,您可以為整個(gè)服務(wù)器保留對(duì)其中之一的引用。

Activiti的工作流程是通過(guò)讀取一個(gè)配置文件,然后得到一個(gè)工作流引擎實(shí)例,通過(guò)這個(gè)引擎可以獲取多個(gè)不同模塊的Service,然后就可以使用這些Service去完成相應(yīng)的接口,比如部署會(huì)使用到RepositoryService,實(shí)例會(huì)用到RuntimeService等。(注意:FormService和IdentityService已經(jīng)在新版本中刪除了)
// 獲取引擎
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
// 獲取Service
RuntimeService runtimeService = processEngine.getRuntimeService();
RepositoryService repositoryService = processEngine.getRepositoryService();
TaskService taskService = processEngine.getTaskService();
ManagementService managementService = processEngine.getManagementService();
IdentityService identityService = processEngine.getIdentityService();
HistoryService historyService = processEngine.getHistoryService();
FormService formService = processEngine.getFormService();
DynamicBpmnService dynamicBpmnService = processEngine.getDynamicBpmnService();
Service總覽
| Service名稱 | 作用 |
|---|---|
| RepositoryService | 資源管理服務(wù) |
| RuntimeService | 流程運(yùn)行管理類 |
| TaskService | 任務(wù)管理類 |
| HistoryService | 歷史管理類 |
| ManagerService | 引擎管理類 |


流程部署
在使用Activiti進(jìn)行流程管理之前,首先需要將建模工具繪制的業(yè)務(wù)流程圖部署到數(shù)據(jù)庫(kù)中,這個(gè)時(shí)候就需要使用RepositoryService,可以通過(guò)RepositoryService進(jìn)行流程部署、查詢流程定義、暫?;蚣せ畎l(fā)布的流程定義等。官方教程
部署流程
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
RepositoryService repositoryService = processEngine.getRepositoryService();
DeploymentBuilder deploymentBuilder = repositoryService.createDeployment();
Deployment deploy = deploymentBuilder
.addClasspathResource("bpmn/stadiumapplication.bpmn")
.name("球場(chǎng)申請(qǐng)流程")
.deploy();
部署方式
ReposityService主要就是依靠調(diào)用DeploymentBuilder的接口來(lái)進(jìn)行流程定義的部署的。DeploymentBuilder支持多種方式的部署。
public interface DeploymentBuilder {
// 文件流方式部署
DeploymentBuilder addInputStream(String resourceName, InputStream inputStream);
DeploymentBuilder addInputStream(String resourceName,Resource resource);
// 資源文件方式部署
DeploymentBuilder addClasspathResource(String resource);
// 字符串內(nèi)容部署,一般是bpmn的xml內(nèi)容字符串部署
DeploymentBuilder addString(String resourceName, String text);
// 字節(jié)數(shù)組部署
DeploymentBuilder addBytes(String resourceName, byte[] bytes);
// Zip壓縮包部署
DeploymentBuilder addZipInputStream(ZipInputStream zipInputStream);
// Bpmn模型部署,可動(dòng)態(tài)生成BPMN model進(jìn)行部署。
DeploymentBuilder addBpmnModel(String resourceName, BpmnModel bpmnModel);
DeploymentBuilder setProjectManifest(ProjectManifest projectManifest);
DeploymentBuilder setEnforcedAppVersion(Integer enforcedAppVersion);
/**
*如果調(diào)用,則不會(huì)對(duì)bpmn2.0xsd進(jìn)行XML模式驗(yàn)證。
*一般不推薦
*/
DeploymentBuilder disableSchemaValidation();
/**
* 如果調(diào)用,則不會(huì)對(duì)流程定義進(jìn)行驗(yàn)證,以確定流程定義在引擎上是可執(zhí)行的。
* 一般不推薦使用。
*/
DeploymentBuilder disableBpmnValidation();
/**
* 為部署指定名字
*/
DeploymentBuilder name(String name);
/**
* 為部署指定種類
*/
DeploymentBuilder category(String category);
/**
* 為部署指定Key,該屬性默認(rèn)是bpmn的id
*/
DeploymentBuilder key(String key);
/**
* 為部署指定租戶ID,沒(méi)有用到過(guò)。。。
*/
DeploymentBuilder tenantId(String tenantId);
/**
* 如果已設(shè)置,則此部署將與以前的任何部署進(jìn)行比較。這意味著每個(gè)(未生成的)資源都將與此部署提供的資源進(jìn)行比較。
*/
DeploymentBuilder enableDuplicateFiltering();
/**
* 設(shè)置激活此部署中包含的流程定義的日期。這意味著所有流程定義都將像往常一樣部署,但它們將從在給定的激活日期之前開(kāi)始。
*/
DeploymentBuilder activateProcessDefinitionsOn(Date date);
/**
* 允許將影響部署的屬性添加到實(shí)例中
*/
DeploymentBuilder deploymentProperty(String propertyKey, Object propertyValue);
/**
* 將所有提供的源部署到Activiti引擎。
*/
Deployment deploy();
}
涉及數(shù)據(jù)庫(kù)表
| 表名 | 作用 |
|---|---|
| ACT_RE_DEPLOYMENT | 流程定義部署表:每部署一次增加一條記錄 |
| ACT_RE_PROCDEF | 流程定義表:部署每個(gè)新的流程都會(huì)在這張表上增加一條記錄 |
| ACT_GE_BYTEARRAY | 流程資源表:存儲(chǔ)xml和png內(nèi)容的表 |
-
ACT_GE_BYTEARRAY
該表記錄了流程的資源信息,包括bpmn文件的xml內(nèi)容和png圖片的二進(jìn)制內(nèi)容,
RepositoryService也提供了相應(yīng)的接口去獲取這些資源。SELECT * FROM ACT_GE_BYTEARRAY;ID_ REV_ NAME_ DEPLOYMENT_ID_ BYTES_ GENERATED_ 2 1 bpmn/stadiumapplication.bpmn 1 (BLOB)4.43 -
ACT_RE_DEPLOYMENT
SELECT * FROM ACT_RE_DEPLOYMENT;ID_ NAME_ CATEGORY_ KEY_ TENANT_ID_ DEPLOY_TIME_ ENGINE_VERSION_ 1 球場(chǎng)申請(qǐng)流程 2021-01-17 08:59:05 -
ACT_RE_PROCDEF
SELECT * FROM ACT_RE_PROCDEF;ID_ REV_ CATEGORY_ NAME_ KEY_ VERSION_ DEPLOYMENT_ID_ RESOURCE_NAME_ DGRM_RESOURCE_NAME_ DESCRIPTION_ HAS_START_FORM_KEY_ HAS_GRAPHICAL_NOTATION_ SUSPENSION_STATE_ TENANT_ID_ ENGINE_VERSION_ ballrepally:1:3 1 http://www.activiti.org/testm1574124674914 球場(chǎng)申請(qǐng) ballrepally 1 1 bpmn/stadiumapplication.bpmn 0 1 1 KEY_這個(gè)字段是用來(lái)唯一識(shí)別不同流程的關(guān)鍵字。而
ACT_RE_DEPLOYMENT和ACT_RE_PROCDEF是一對(duì)多的關(guān)系,即:一次部署可以部署多個(gè)流程定義,而只會(huì)在流程部署表(ACT_RE_DEPLOYMENT)生成一條記錄。一般情況下建議一次部署只部署一個(gè)流程。
查詢流程信息
-
查詢流程部署信息
// 獲取流程部署查詢器 DeploymentQuery deploymentQuery = repositoryService.createDeploymentQuery(); // 查詢列表。 List<Deployment> deployments = deploymentQuery.list();
-
查詢流程定義信息
// 獲取流程定義查詢器 ProcessDefinitionQuery processDefinitionQuery = repositoryService.createProcessDefinitionQuery(); // 查詢器構(gòu)建查詢條件,最終返回列表。 List<ProcessDefinition> processDefinitionList = processDefinitionQuery .orderByProcessDefinitionVersion() .asc() .list(); -
查詢部署資源
public interface RepositoryService { /** * 通過(guò)部署ID和資源名稱獲取資源流。 * @param deploymentId 部署ID * @param resourceName 資源名稱 */ InputStream getResourceAsStream(String deploymentId, String resourceName); /** * 檢索給定部署的部署資源列表,按字母順序排列。 * @param deploymentId 部署ID */ List<String> getDeploymentResourceNames(String deploymentId); /** * 允許通過(guò)字節(jié)流訪問(wèn)已部署的流程模型,例如BPMN2.0XML文件。 * @param processDefinitionId 流程定義ID */ InputStream getProcessModel(String processDefinitionId); /** * 返回流程定義信息 * @param processDefinitionId 流程定義ID */ ProcessDefinition getProcessDefinition(String processDefinitionId); /** * 返回與具有提供的進(jìn)程定義id的進(jìn)程定義對(duì)應(yīng)的BPMN 模型。 * @param processDefinitionId 流程定義ID */ BpmnModel getBpmnModel(String processDefinitionId); }
掛起流程
public interface RepositoryService {
/**
* 掛起給定id的流程定義。
*/
void suspendProcessDefinitionById(String processDefinitionId);
/**
* 掛起給定id的流程定義。
*
* @param suspendProcessInstances 是否掛起流程實(shí)例
* 如果為true,則提供的流程定義的所有流程實(shí)例也將掛起
* @param suspensionDate 掛起日期
* 進(jìn)程定義將被掛起的日期。如果為null,進(jìn)程定義將立即掛起。注意:作業(yè)執(zhí)行器需要處于活動(dòng)狀態(tài)才能使用此命令!
*/
void suspendProcessDefinitionById(String processDefinitionId,
boolean suspendProcessInstances,
Date suspensionDate);
/**
* 掛起給定流程定義Key的流程
*/
void suspendProcessDefinitionByKey(String processDefinitionKey);
/**
* 掛起給定流程定義Key的流程
* @param suspendProcessInstances 是否掛起流程實(shí)例
* 如果為true,則提供的流程定義的所有流程實(shí)例也將掛起
* @param suspensionDate 掛起日期
* 進(jìn)程定義將被掛起的日期。如果為null,進(jìn)程定義將立即掛起。注意:作業(yè)執(zhí)行器需要處于活動(dòng)狀態(tài)才能使用此命令!
*/
void suspendProcessDefinitionByKey(String processDefinitionKey,
boolean suspendProcessInstances,
Date suspensionDate);
/**
* 掛起給定流程定義Key和租戶Id的流程。
*/
void suspendProcessDefinitionByKey(String processDefinitionKey, String tenantId);
...
}
激活流程
public interface RepositoryService {
/**
* 激活指定流程定義ID的流程
*/
void activateProcessDefinitionById(String processDefinitionId);
/**
* 激活指定流程定義ID的流程
*
* @param suspendProcessInstances 是否掛起流程實(shí)例
* 如果為true,則提供的流程定義的所有流程實(shí)例也將激活
* @param suspensionDate 掛起日期
* 進(jìn)程定義將被激活的日期。如果為null,進(jìn)程定義將立即激活。注意:作業(yè)執(zhí)行器需要處于掛起狀態(tài)才能使用此命令!
*/
void activateProcessDefinitionById(String processDefinitionId,
boolean activateProcessInstances,
Date activationDate);
/**
* 激活給定流程定義Key(BPMN圖中的ID)的流程
*/
void activateProcessDefinitionByKey(String processDefinitionKey);
/**
* 激活指定流程定義Key的流程
*
* @param suspendProcessInstances 是否掛起流程實(shí)例
* 如果為true,則提供的流程定義的所有流程實(shí)例也將激活
* @param suspensionDate 掛起日期
* 進(jìn)程定義將被激活的日期。如果為null,進(jìn)程定義將立即激活。注意:作業(yè)執(zhí)行器需要處于掛起狀態(tài)才能使用此命令!
*/
void activateProcessDefinitionByKey(String processDefinitionKey,
boolean activateProcessInstances,
Date activationDate);
/**
* 激活給定流程定義Key和租戶Id的流程
*/
void activateProcessDefinitionByKey(String processDefinitionKey, String tenantId);
...
}
刪除流程
public interface RepositoryService {
/**
* 刪除指定流程.
* @param deploymentId 流程部署ID
*/
void deleteDeployment(String deploymentId);
/**
* 刪除給定的部署和級(jí)聯(lián)刪除到流程實(shí)例、歷史流程實(shí)例和作業(yè)。
*
* @param deploymentId 流程部署ID
* @param cascade 是否級(jí)聯(lián)刪除
*/
void deleteDeployment(String deploymentId, boolean cascade);
}
流程實(shí)例
當(dāng)我們將流程部署完成之后,先要使用他就得先開(kāi)始一個(gè)流程實(shí)例。所謂流程實(shí)例,即比如我們部署了一個(gè)請(qǐng)假流程,小李的請(qǐng)假流程就是一個(gè)實(shí)例,小王的請(qǐng)假流程也是一個(gè)實(shí)例。所以我們的流程實(shí)例也是基于流程部署來(lái)實(shí)現(xiàn)的。
流程實(shí)例的管理底層使用的是RuntimeService,而最新提供的ProcessRuntime對(duì)流程進(jìn)行了二次封裝,簡(jiǎn)便了api的調(diào)用。
啟動(dòng)流程實(shí)例
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
RuntimeService runtimeService = processEngine.getRuntimeService();
public interface RuntimeService {
/**
* 啟動(dòng)先前創(chuàng)建的流程實(shí)例。
* @param createdProcessInstance 已經(jīng)創(chuàng)建的流程實(shí)例
*/
ProcessInstance startCreatedProcessInstance(ProcessInstance createdProcessInstance, Map<String, Object> variables);
/**
* 開(kāi)啟指定流程定義Key的最新版本
* @param processDefinitionKey 流程定義的Key
*/
ProcessInstance startProcessInstanceByKey(String processDefinitionKey);
/**
* 開(kāi)啟給定流程定義Key的最新版本,并指定業(yè)務(wù)流程Key
* @param processDefinitionKey 流程定義Key
* @param businessKey 業(yè)務(wù)流程Key
*/
ProcessInstance startProcessInstanceByKey(String processDefinitionKey, String businessKey);
/**
* 開(kāi)啟指定流程定義Key的最新版本
*
* @param processDefinitionKey 流程定義的Key
* @param variables 流程的參數(shù),比如uel參數(shù)
*/
ProcessInstance startProcessInstanceByKey(String processDefinitionKey, Map<String, Object> variables);
/**
* 開(kāi)啟給定流程定義Key的最新版本,并指定業(yè)務(wù)流程Key
* @param processDefinitionKey 流程定義Key
* @param variables 參數(shù)
* @param businessKey 業(yè)務(wù)Key
*/
ProcessInstance startProcessInstanceByKey(String processDefinitionKey, String businessKey, Map<String, Object> variables);
/**
* 開(kāi)啟指定流程定義Id的最新版本.
* @param processDefinitionId 流程定義Id
*/
ProcessInstance startProcessInstanceById(String processDefinitionId);
/**
* 開(kāi)啟給定流程定義Id的最新版本,并指定業(yè)務(wù)流程Key
* @param processDefinitionId 流程定義ID
*/
ProcessInstance startProcessInstanceById(String processDefinitionId, String businessKey);
/**
* 開(kāi)啟給定流程定義Id的最新版本
* @param processDefinitionId 流程定義Id
* @param variables 參數(shù)
*/
ProcessInstance startProcessInstanceById(String processDefinitionId, Map<String, Object> variables);
/**
* 開(kāi)啟給定流程定義Id的最新版本
* @param processDefinitionId 流程定義Id
* @param variables 參數(shù)
*/
ProcessInstance startProcessInstanceById(String processDefinitionId, String businessKey, Map<String, Object> variables);
}
涉及數(shù)據(jù)庫(kù)表
-
ACT_RU_EXECTION
流程實(shí)例執(zhí)行表,記錄當(dāng)前流程實(shí)例的執(zhí)行情況。流程實(shí)例執(zhí)行,如果當(dāng)前只有一個(gè)分支時(shí),一個(gè)流程實(shí)例只有一條記錄且執(zhí)行表的主鍵id和流程實(shí)例id相同,如果當(dāng)前有多個(gè)分支正在運(yùn)行則該執(zhí)行表中有多條記錄,存在執(zhí)行表的主鍵和流程實(shí)例id不相同的記錄。
不論當(dāng)前有幾個(gè)分支總會(huì)有一條記錄的執(zhí)行表的主鍵和流程實(shí)例id相同
一個(gè)流程實(shí)例運(yùn)行完成,此表中與流程實(shí)例相關(guān)的記錄刪除。
-
ACT_RU_TASK
任務(wù)執(zhí)行表,記錄當(dāng)前執(zhí)行的任務(wù)。啟動(dòng)流程實(shí)例,流程當(dāng)前執(zhí)行到第一個(gè)任務(wù)結(jié)點(diǎn),此表會(huì)插入一條記錄表示當(dāng)前任務(wù)的執(zhí)行情況,如果任務(wù)完成則記錄刪除。
-
ACT_RU_IDENTITYLINK
任務(wù)參與者,記錄當(dāng)前參與任務(wù)的用戶或組。
-
ACT_HI_PROCINST
流程實(shí)例歷史表,流程實(shí)例啟動(dòng),會(huì)在此表插入一條記錄,流程實(shí)例運(yùn)行完成記錄也不會(huì)刪除。
-
ACT_HI_TASKINST
任務(wù)歷史表,記錄所有任務(wù),開(kāi)始一個(gè)任務(wù),不僅在act_ru_task表插入記錄,也會(huì)在歷史任務(wù)表插入一條記錄,任務(wù)歷史表的主鍵就是任務(wù)id,任務(wù)完成此表記錄不刪除。
-
ACT_HI_ACTINST
活動(dòng)歷史表,記錄所有活動(dòng),活動(dòng)包括任務(wù),所以此表中不僅記錄了任務(wù),還記錄了流程執(zhí)行過(guò)程的其它活動(dòng),比如開(kāi)始事件、結(jié)束事件。
掛起流程實(shí)例
/**
* 掛起指定流程Id的流程實(shí)例
*/
void suspendProcessInstanceById(String processInstanceId);
激活流程實(shí)例
/**
* 激活指定流程Id的流程實(shí)例
*/
void activateProcessInstanceById(String processInstanceId);
刪除流程實(shí)例
/**
* 刪除實(shí)例
* @param processInstanceId 流程實(shí)例Id
* @param deleteReason 刪除原因
*/
void deleteProcessInstance(String processInstanceId, String deleteReason);
獲取傳入?yún)?shù)
/**
* 給定執(zhí)行作用域(包括父作用域)中可見(jiàn)的所有變量。
* @param executionId 啟動(dòng)的實(shí)例的ID
*/
Map<String, Object> getVariables(String executionId);
任務(wù)管理
任務(wù)管理的接口是通過(guò)
TaskService來(lái)實(shí)現(xiàn)的。而最新的是使用TaskRuntime接口來(lái)實(shí)現(xiàn)的,當(dāng)然TaskRuntime底層也是通過(guò)TaskService來(lái)實(shí)現(xiàn)的。
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
TaskService taskService = processEngine.getTaskService();
獲取我的任務(wù)
// 獲取任務(wù)查詢器
TaskQuery taskQuery = taskService.createTaskQuery();
// 查詢器獲取列表
List<Task> tasks = taskQuery.taskAssignee("you ID").list();
拾取任務(wù)
有的時(shí)候,某個(gè)任務(wù)節(jié)點(diǎn)可能沒(méi)有直接指定責(zé)任人,但是指定了候選人列表或者候選組。這個(gè)時(shí)候你還是可以將這個(gè)任務(wù)查詢出來(lái)的,但是需要主動(dòng)拾取任務(wù)。
TaskQuery taskQuery = taskService.createTaskQuery();
Task task = taskQuery.processInstanceBusinessKey("businessKey").singleResult();
// 拾取任務(wù)
taskService.claim(task.getId(),"userId");
解開(kāi)任務(wù)
如果拾取任務(wù)錯(cuò)了或者想將任務(wù)轉(zhuǎn)給別人,可以調(diào)用解開(kāi)任務(wù)的接口,其實(shí)也就是將責(zé)任人置空
taskService.unclaim(String taskId);
完成任務(wù)
/**
* 在任務(wù)成功執(zhí)行時(shí)調(diào)用,并且所需的任務(wù)參數(shù)由最終用戶給定。
* @param taskId 任務(wù)ID
* @param variables 參數(shù)對(duì)象
*/
void complete(String taskId, Map<String, Object> variables);