概述
Activiti 7 提供 Activiti Cloud 和 Activiti Core 两种使用方式。前者以 Cloud Native 方式提供云端部署并以微服务方式使用;后者仍然可以集成到产品中使用(由集成着自己封装微服务)。
新的 API 封装在 org.activiti.api 包中。目前网上针对这些新 API 的使用方法介绍文档非常少,除了官方 Activiti Core 简单介绍外,就只剩下 Activiti Core Examples 样例代码。
本文尝试对新 API 的使用方法做些简单说明,供大家使用 Activiti 7 时进行参考。
本文的读者是熟悉 Activiti 基本原理,能熟练使用 Activiti 5/6,并希望了解 Activiti 7.x 新提供的 XXXRuntime API 的开发人员,
Activiti 7.x 新 API 的设计思路
从现有官方文档和样例分析看来,Activiti 7.x 新 API 在设计方面做了以下工作:
- 与 Spring Boot 及 Spring Security 做了深度集成。包括流程发起者(Initiator),用户任务(User Task)的执行人(Assignee)等,直接从 Sprint Security 的 Security Context 中获取。当项目集成了 Spring Security 后,会非常方便。
- 服务任务(Service Task)可直接调用 Srping 的 Bean(org.activiti.api.process.runtime.connector.Connector 类型);流程流转过程中的各类事件(如流程启动/完成、任务开始/结束、变量创建等等),均可通过创建 Spring 的 Bean,并返回相应类型来监听和处理。
- 对流程流转过程中的变量(Variable)访问方式进行了重新设计:变量分为流程级别(Process)和任务级别(Task),可分别通过 ProcessRuntime 和 TaskRuntime 进行访问。当然底层实现仍然与 Execution 绑定。个人认为,这部分的设计并不完善,会给流程过程造成很多麻烦。规避方式就是仍然使用原有的 XXXService 接口。详见后文样例。
需说明一点:Activiti 7.x 向前兼容,即原有的 XXXService API 仍然可继续使用。新的 XXXRuntime API 是对原有 XXX Service API 进行了一层封装而已。
上述几点工作中,Spring 和 Spring Security 集成方式很好理解,参考其样例代码即可。本文对其新的 Variable 设计和使用方式进行分析说明。
Activiti 7.x 中的变量(Variable)
Activiti 7.x 中的变量(Variable)可分为两级:
- 流程级:整个流程过程中均能访问。在流程启动时可设置,在服务任务(Service Task)中可通过 OutBoundVariable 并配合 -extensions.json 配置文件进行设置。
- 任务级:仅用户任务( User Task)中使用的变量。
下面代码演示如何通过新的 API 访问这两类变量:
void listProcessVariables(String processInstanceId) {
log.info(">>> Process Variables:");
List variables = processRuntime.variables(
ProcessPayloadBuilder.variables().withProcessInstanceId(processInstanceId).build());
for (VariableInstance vi : variables) {
log.info("t> " + vi.getName() + " -> " + vi.getValue());
}
}
void listTaskVariables(String taskId) {
log.info(">>> Task Variables:");
List variables = taskRuntime.variables(
TaskPayloadBuilder.variables().withTaskId(taskId).build());
for (VariableInstance vi : variables) {
log.info("t> " + vi.getName() + " -> " + vi.getValue());
if ("content".equals(vi.getName())) {
}
}
}
注意:processRuntime 提供 API 来设置变量,但其结合 Spring Security 对调用者身份进行了验证:仅允许流程的发起者执行此操作。
因此用户任务( User Task)暂时没有找到设置流程级变量的方法。
两类变量的区别可参考下表:
| 变量类型 | 设置方式 | 获取方式 |
|---|---|---|
| 流程级 |
|
|
| 任务级 |
|
|
在 Activiti 7 中,流程定义文件缺省放置在 src/main/resources/processes/ 路径下,Spring Boot 启动时会自动从该路径下加载流程定义文件(*.bpmn20.xml)。
此外,还会同时加载 -extensions.json 配置文件。该配置文件目前在网上没有找到任何文档说明,因此只能通过测试和阅读 Activiti 源代码获取一些基本用法(Activiti 源代码没有注释,很郁闷)。
以下为此扩展配置文件中对变量(Variable)控制的一些基本配置信息,供参考。
选取配置文件为 activiti-api-basic-connector-example 中的例子进行修改,如下:
{
"id": "RankMovieIdModel",
"extensions": {
"RankMovieId": {
"mappings": {
"serviceTaskId": {
"inputs": {
"movieName": {
"type": "variable",
"value": "movieToRank"
}
},
"outputs": {
"movieDesc": {
"type": "variable",
"value": "movieDescription"
}
}
},
"userTaskId": {
"inputs": {
"movieName": {
"type": "variable",
"value": "movieToRank"
},
"movieDescription": {
"type": "variable",
"value": "movieDesc"
}
}
}
},
"properties": {
"movieToRankId": {
"id": "movieToRankId",
"name": "movieToRank",
"type": "string",
"required": true
},
"movieDescId": {
"id": "movieDescId",
"name": "movieDesc",
"type": "string",
"required": false
}
}
}
}
}
配置文件中,extensions.RankMovieId 指定了此文件对 ID 为 RankMovieId 的流程生效(对应到 *.bpmn20.xml 中流程定义的 ID)。包含两个部分:
- mappings:对流程中 ServiceTask 和 UserTask 使用的变量进行控制。
- properties:对流程级变量的类型进行说明。
接下来结合样例代码,我们看下这些配置的作用。
样例流程定义如下:
流程启动后,先进入一个 Service Task,从 Service Task 中获取到 Movie 的 Description,然后进入 Add Rating 的 User Task,由用户对这个 Movie 打分(Rating)。
启动流程的代码如下:
ProcessInstance startProcess() {
ProcessInstance processInstance = processRuntime.start(ProcessPayloadBuilder.start()
.withProcessDefinitionKey("RankMovieId")
.withName("myProcess")
.withVariable("movieToRank", "Lord of the rings").build());
return processInstance;
}
启动流程时,通过 withVariable 创建了一个流程级别的变量:movieToRank。
在接下来执行的 ServiceTask 时(TaskID 为 serviceTaskId),通过 extentions.json 定义了一个名称为 movieName 的变量给 ServiceTask 使用,其值为 movieToRank 流程变量的值。
于是 ServiceTask 的处理代码可以访问此变量值:
@Bean("Movies.getMovieDesc")
public Connector getMovieDesc() {
return integrationContext -> {
Map inBoundVariables = integrationContext.getInBoundVariables();
logger.info(">> inbound: " + inBoundVariables);
integrationContext.addOutBoundVariable("movieDescription",
"The Lord of the Rings is an epic high fantasy novel written by English author and scholar J. R. R. Tolkien");
return integrationContext;
};
}
上面代码输出的日志中,将输出:
>> inbound: {movieName=Lord of the rings}
注意在 Task 中拿到的变量名称为 movieName,不再是流程变量中的 movieToRank。
该 Task 中增加一个输出变量:movieDescription,结合 extentions.json 中定义的输出变量名 movieDesc,任务执行完成后,将创建一个新的流程级变量:movieDesc(注意是以 extensions.json 中定义的变量名为准,而不是 addOutBoundVariable 中传入的变量名参数)。
接下来在 UserTask 中,可以通过 ProcessRuntime 获取进程级变量。上面的 extentions.json 中定义了两个 UserTask 的输入变量 movieName 和 movieDescription,因此也可以通过 TaskRuntime 获取到任务级的变量(注意变量名与流程级的变量名不同)。
UserTask 完成时,可传入新的变量 rating:
void completeAvailableTasks() {
Page tasks = taskRuntime.tasks(Pageable.of(0, 20));
tasks.getContent().forEach(task -> {
logger.info(">>> Performing task -> " + task);
listTaskVariables(task);
taskRuntime.complete(TaskPayloadBuilder.complete()
.withTaskId(task.getId())
.withVariable("rating", 5).build());
});
}
注意:这个新的变量 rating 只能作为任务级别变量,无法成为流程级别,即使 extensions.json 中增加了 outputs 定义,也不行(实测无效)。
问题及规避
目前的设计,无法在 UserTask 中设置流程级别的变量。前文已经说过,即使用 ProcessRuntime 也无法设置,仅当 UserTask 的 Assignee 是流程的创建者才行。
这会带来一些问题:例如在子流程 UserTask 中设置的值,无法作为变量用于父流程中 Exclusive Gateway 的判断表达式。
这时,只能使用传统的 runtimeService 方法,设置流程全局变量:
String executionId = taskService.createTaskQuery().taskId(task.getId()).list().get(0).getExecutionId(); runtimeService.setVariable(executionId, "approved", approved);
结论
Activiti 7 的新 Runtime API 设计思路很好,但并不完善。
实际使用过程中,往往需要结合原有的 Service API,才能实现完整的功能。



