从零开始 Spring Boot 21:Activiti
图源:简书 (jianshu.com)
Activiti是一个开源的工作流引擎,可以帮助我们实现一些流程自动化,比如OA审批流等。
官网:Open Source Business Automation | Activiti
整合
添加依赖
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-spring-boot-starter-basic</artifactId>
<exclusions>
<exclusion>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
</exclusion>
</exclusions>
<version>6.0.0</version>
</dependency>
mvn中最新的依赖版本是6.0.0,可以通过下面的页面查看所有的mvn依赖版本:
这里通过exclusion 标签屏蔽了项目对activitii 包中的mybatis 的传递依赖,这是因为项目本身引用MybatisPlus ,并依赖相应的Mybatis ,如果这里再通过activiti 引入其他版本的Mybatis 就会导致版本冲突,会导致无法正常运行。
添加配置
# ...
# activity
# 是否每次启动时检查数据库表需要更新
spring.activiti.database-schema-update=false
# 是否检查存在流程配置文件
spring.activiti.check-process-definitions=false
# 流程配置文件目录
spring.activiti.process-definition-location-prefix=classpath:/processes/
# 流程配置文件后缀名
spring.activiti.process-definition-location-suffixes[0]=**.bpmn
spring.activiti.process-definition-location-suffixes[1]=**.bpmn20.xml
# 保存历史数据级别,full为最高
spring.activiti.history-level=full
修改注解
package cn.icexmoon.demo.books;
import org.activiti.spring.boot.SecurityAutoConfiguration;
@SpringBootApplication(exclude = SecurityAutoConfiguration.class)
@MapperScan("cn.icexmoon.demo.books.*.mapper")
public class BooksApplication {
public static void main(String[] args) {
}
}
需要修改SpringBootApplication 注解,通过exclude 属性排除对SecurityAutoConfiguration 类的自动化配置。
建表
Activiti本身需要数据库支持才能工作,所以需要创建其需要的表结构。Activiti官方提供建表所需的sql,这包含在Activiti的Jar包(activity-engine-x.x.x.jar)中。
首先,在mvn仓库中找到下载的目标jar包:
用解压工具直接解压该jar包,可以在db 目录下找到这样几个目录:
这几个目录分别对应创建表、删除表、更新表等的DLL SQL语句,这里我们只需要用到creat。
创建语句是按照数据库分类的,因为虽然SQL语法是通用的,但是因为数据库类型和版本的不同,在某些特性上会有差异导致SQL语句不会完全相同。
这里按照项目自身使用的数据库类型选择即可,我这里是MySQL。
之后就是将这些建表的DDL导入数据库了,选择自己顺手的工具即可,我这里是使用sqlyog。
其实Activiti可以在启动后扫描数据库,如果缺少相应的表结构会自动创建,但是这里我整合后并不能自动创建表结构,进而因为缺少表结构无法启动应用,不知道是不是配置的问题。
此外,很多商业部署因为安全方面考虑,生产环境是没有DDL语句的数据库执行权限的,因此需要通过上述方式提取DDL语句后提交给DBA来完成建表工作。、
开始
制作流程
Activiti使用的是由BPMN2.0标准定义的流程图,有很多工具都可以制作该标准的流程图,使用最多的是在IDE中集成的各种BPMN相关插件。不过我这里使用Activiti官方提供的一个在线工具:
使用该工具可以绘制一个最简单的流程图:
该流程图仅包含3个最简单的元素:
- StartEvent,流程的开始。
- Task,流程中需要执行的动作,可能是用户审批或者某些代码完成的自动化工作。
- EndEvent,流程结束。
用在线工具绘制好后可以通过左下角菜单导出BMPN2.0文件,我们需要将这个文件保存到Spring Boot的静态资源目录下的processes 目录下:
实际上该文件是一个XML文件:
<?xml version="1.0" encoding="UTF-8"?>
<bpmn:definitions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" id="Definitions_1kwvfrz" targetNamespace="http://bpmn.io/schema/bpmn" exporter="bpmn-js (https://demo.bpmn.io)" exporterVersion="9.3.2">
<bpmn:process id="Process_0wu4lop" isExecutable="true">
<bpmn:startEvent id="StartEvent_0vx5axl">
<bpmn:outgoing>Flow_0nv17f1</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:endEvent id="Event_1294r00">
<bpmn:incoming>Flow_1ke9x09</bpmn:incoming>
</bpmn:endEvent>
<bpmn:task id="Activity_0nt5d38" name="approve">
<bpmn:incoming>Flow_0nv17f1</bpmn:incoming>
<bpmn:outgoing>Flow_1ke9x09</bpmn:outgoing>
</bpmn:task>
<bpmn:sequenceFlow id="Flow_0nv17f1" sourceRef="StartEvent_0vx5axl" targetRef="Activity_0nt5d38" />
<bpmn:sequenceFlow id="Flow_1ke9x09" sourceRef="Activity_0nt5d38" targetRef="Event_1294r00" />
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_0wu4lop">
<bpmndi:BPMNEdge id="Flow_0nv17f1_di" bpmnElement="Flow_0nv17f1">
<di:waypoint x="210" y="118" />
<di:waypoint x="210" y="200" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1ke9x09_di" bpmnElement="Flow_1ke9x09">
<di:waypoint x="210" y="280" />
<di:waypoint x="210" y="352" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_0vx5axl">
<dc:Bounds x="192" y="82" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_1294r00_di" bpmnElement="Event_1294r00">
<dc:Bounds x="192" y="352" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0nt5d38_di" bpmnElement="Activity_0nt5d38">
<dc:Bounds x="160" y="200" width="100" height="80" />
<bpmndi:BPMNLabel />
</bpmndi:BPMNShape>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>
这个文件包含两部分,process 节点中包含的是流程定义,BPMNDiagram 节点中包含的是流程对应的可视化图形。
从process 包含这么几种子节点:
startEvent ,对应流程图的StartEventendEvent ,对应流程图的EndEventtask ,对应流程图中的TasksequenceFlow ,对应流程图中连接Event和Task的线段
通过sequenceFlow 的sourceRef 属性和targetRef 属性可以很清楚看出这些流程元素的关联关系。
部署流程
流程文件准备好后需要装载(部署)到Activiti中才可以使用,分为两种方式:自动和手动。
先来看自动部署,只要将activiti 的相关配置设置为true :
# ...
# 是否检查存在流程配置文件
spring.activiti.check-process-definitions=true
# ...
这样项目启动后Activiti会自动检索processes 目录下的流程文件进行加载。
实际上该配置的默认值就是true,也就是说缺省时的行为就是自动加载。
当然也可以手动部署,这需要将上边说的Acitiviti配置设置为false ,并且在Spring Boot的入口类中注入一个CommandLineRunner :
package cn.icexmoon.demo.books;
@SpringBootApplication(exclude = SecurityAutoConfiguration.class)
@MapperScan("cn.icexmoon.demo.books.*.mapper")
@Log4j2
public class BooksApplication {
public static void main(String[] args) {
SpringApplication.run(BooksApplication.class, args);
}
@Bean
public CommandLineRunner init(final RepositoryService repositoryService,
final RuntimeService runtimeService,
final TaskService taskService
) {
return new CommandLineRunner() {
@Override
public void run(String... args) throws Exception {
repositoryService.createDeployment()
.addClasspathResource("processes/diagram.bpmn")
.key("Process_0wu4lop")
.name("示例流程")
.deploy();
log.debug("流程{}已部署", "示例流程");
}
};
}
}
这样就可以在应用启动后部署指定流程。
部署流程后就可以在Activiti的数据表中看到相关数据了,Activiti用表名前缀来区分不同用途的表:
- ACT_RE_,
re 是repository (仓库)的缩写,即流程相关的静态信息,包括流程定义和流程资源(图片,规则)等。 - ACT_RU_,
ru 是runtime (运行时)的缩写,这些表包含流程在运行时产生的数据,包括(流程实例、用户任务、变量、定时任务等)。Activiti会在流程实例启动时存储相关数据,并在流程实例结束时移除这些数据,这样可以保持一个精简的表数据规模,以维持高效的数据库性能。 - ACT_ID_,
id 是identity 的缩写,这些表包含了相关的ID信息,包括用户、用户组等。 - ACT_HI_,
hi 是history 的缩写,这些表包含了历史数据,包括运行过的流程实例、变量、任务等。 - ACT_GE_,
ge 是general (通用)的缩写,这些表包含了一般性的数据。
时间检查表就会发现下面这些表产生了数据:
act_ge_bytearray
显然这个表保存的是从processes 加载的BPMN文件信息。
act_re_deployment
这个包含的是我们部署流程时的信息,其中SpringAutoDeployment 是自动部署时产生的部署信息。
act_re_procdef
这个表包含了流程定义,其中HAS_START_FORM_KEY_ 字段表示流程是否启动。
当然实际使用中我们并不需要每次在项目启动时部署流程,我们可以将流程部署进行封装:
public interface IActivitiService {
void deploy(String classPathResource, String key, String name);
}
package cn.icexmoon.demo.books.book.service.impl;
@Log4j2
@Service
public class ActivitiServiceImpl implements IActivitiService {
@Autowired
private RepositoryService repositoryService;
@Autowired
private TaskService taskService;
@Autowired
private RuntimeService runtimeService;
@Override
public void deploy(String classPathResource, String key, String name) {
log.info("========开始部署流程=======");
log.info("资源路径:" + classPathResource);
log.info("key:" + key);
log.info("名称:" + name);
DeploymentBuilder deployment = repositoryService.createDeployment();
deployment
.addClasspathResource(classPathResource)
.key(key)
.name(name)
.deploy();
log.info("=======流程已部署=========");
}
}
这里我们通过自动装配获取了几个Activiti的核心对象,Activiti通过这几个核心对象的API对外提供服务:
RepositoryService ,流程仓库服务,用来管理Activiti的静态资源,比如部署后的流程等。TaskService ,流程任务服务,用来管理流程任务,通过它可以获取到当前有多少任务以及某个人需要处理的任务等。RuntimeService ,流程运行时服务,用来管理流程的运行相关功能,比如获取运行时产生的数据或启动某个流程实例。
在调试中我发现通过mvn启动的项目是无法加断点debug的,原因是mvn启动的是独立的进程,所以需要通过添加JVM启动参数并远程调试的方式进行debug,具体可以参考spring-boot-maven-plugin:debug调试程序_lyterrific的博客-CSDN博客_maven springboot 调试。
在Controller中编写Handler方法:
package cn.icexmoon.demo.books.book.controller;
@RestController
@RequestMapping("/activiti")
@Api(tags = "Activiti示例接口")
public class ActivityController {
@Resource
private RuntimeService runtimeService;
@Autowired
private IActivitiService activitiService;
@Data
private static class DeployProcessDTO {
@ApiModelProperty(value = "流程定义资源文件(bpmn文件的classPath路径)", required = true, example = "processes/diagram.bpmn")
@NotBlank
private String classPathResource;
@ApiModelProperty(value = "流程key(bpmn文件中流程的key)", required = true, example = "Process_0wu4lop")
@NotBlank
private String key;
@ApiModelProperty(value = "部署后的流程名称,可以为null", example = "A example process")
private String name;
}
@ApiOperation("部署流程")
@PostMapping("/deploy")
public Result deployProcess(@RequestBody DeployProcessDTO dto) {
activitiService.deploy(dto.getClassPathResource(), dto.getKey(), dto.getName());
return Result.success();
}
}
之后就可以通过接口调试工具部署流程了:
流程定义
流程部署后,会以流程定义(process definition)的形式存在,我们可以通过RepositoryService 获取当前所有的流程定义:
package cn.icexmoon.demo.books.book.service.impl;
@Log4j2
@Service
public class ActivitiServiceImpl implements IActivitiService {
@Autowired
private RepositoryService repositoryService;
@Autowired
private TaskService taskService;
@Autowired
private RuntimeService runtimeService;
public List<ProcessDefinition> listProcessDefinition() {
return repositoryService.createProcessDefinitionQuery().list();
}
}
当然还需要编写对应的Controller的Handler方法:
package cn.icexmoon.demo.books.book.controller;
@RestController
@RequestMapping("/activiti")
@Api(tags = "Activiti示例接口")
public class ActivityController {
@Resource
private RuntimeService runtimeService;
@Autowired
private IActivitiService activitiService;
@Data
@Accessors(chain = true)
private static class GetProcessDefineListVO {
@ApiModelProperty("部署流程时的deployment的id,可以用于删除流程部署")
private String deploymentId;
@ApiModelProperty("流程定义id")
private String id;
@ApiModelProperty("流程定义名称")
private String name;
@ApiModelProperty("流程的key")
private String key;
@ApiModelProperty("bpmn资源文件名称")
private String resourceName;
@ApiModelProperty("流程定义版本")
private Integer version;
public static GetProcessDefineListVO newInstance(ProcessDefinition pd) {
GetProcessDefineListVO vo = new GetProcessDefineListVO();
vo.setId(pd.getId())
.setName(pd.getName())
.setDeploymentId(pd.getDeploymentId())
.setKey(pd.getKey())
.setResourceName(pd.getResourceName())
.setVersion(pd.getVersion());
return vo;
}
}
@ApiOperation("获取流程定义列表")
@PostMapping("/process-define/list")
public List<GetProcessDefineListVO> getProcessDefineList() {
List<ProcessDefinition> pds = activitiService.listProcessDefinition();
List<GetProcessDefineListVO> vo = pds.stream().map(pd -> GetProcessDefineListVO.newInstance(pd)).collect(Collectors.toList());
return vo;
}
}
最后通过接口调用可以获取到类似下面这样的信息:
{
"success": true,
"msg": "",
"data": [
{
"deploymentId": "27501",
"id": "Process_0wu4lop:10:27504",
"name": "A test process",
"key": "Process_0wu4lop",
"resourceName": "processes/diagram.bpmn",
"version": 10
},
{
"deploymentId": "2501",
"id": "Process_0wu4lop:1:2504",
"name": null,
"key": "Process_0wu4lop",
"resourceName": "processes/diagram.bpmn",
"version": 1
},
{
"deploymentId": "12501",
"id": "Process_0wu4lop:2:12505",
"name": null,
"key": "Process_0wu4lop",
"resourceName": "D:\\workspace\\learn_spring_boot\\ch21\\books\\target\\classes\\processes\\diagram.bpmn",
"version": 2
},
{
"deploymentId": "12501",
"id": "oneTaskProcess:1:12506",
"name": "The One Task Process",
"key": "oneTaskProcess",
"resourceName": "D:\\workspace\\learn_spring_boot\\ch21\\books\\target\\classes\\processes\\one-task-process.bpmn20.xml",
"version": 1
}
],
"code": 200
}
可以看到即使是相同的流程定义文件(bpmn)部署的流程,也是不同的“流程定义”,只不过它们之间有着一些相同的联系:
- 具有相同的key
- 具有相同的resourceName(自动部署的流程定义用的是绝对路径)
- 版本号逐次递增
流程定义的id由3部分组成:流程key、版本号、部署器id。
deploymentId 是部署(employment,这里是名词,表示某次部署的结果)的id。是不是"同一批"部署的可以用deploymentId 来区分,比如Process_0wu4lop:2:12505 和oneTaskProcess:1:12506 就是同一个部署(employment)自动部署的。
删除部署
流程部署后,如果要删除,必须按照流程所属的部署来进行删除。也就是说通过部署的id将部署相关的流程全部删除:
package cn.icexmoon.demo.books.book.service.impl;
@Log4j2
@Service
public class ActivitiServiceImpl implements IActivitiService {
@Override
public void delDeployment(String deploymentId, boolean cascadeDel) {
repositoryService.deleteDeployment(deploymentId, cascadeDel);
}
}
RepositoryService.deleteDeployment 有两个参数,第一个参数是要删除的部署的id,第二个参数是是否要级联删除。如果是级联删除,会删除相关流程定义的所有内容,包括正在执行的流程实例和历史信息。如果是非级联删除,只会删除部署,但如果相关流程有正在进行中的流程实例,就会报错,无法删除。
启动流程
可以用流程key启动一个对应的流程实例:
package cn.icexmoon.demo.books.book.service.impl;
@Log4j2
@Service
public class ActivitiServiceImpl implements IActivitiService {
@Autowired
private RepositoryService repositoryService;
@Autowired
private TaskService taskService;
@Autowired
private RuntimeService runtimeService;
@Override
public ProcessInstance startProcessInstance(String processKey) {
logInfo();
ProcessInstance process = runtimeService.startProcessInstanceByKey(processKey);
log.info("流程" + processKey + "已启动一个新实例");
logInfo();
return process;
}
}
在Handler方法中通过返回的ProcessInstance 可以获取一些生成的流程实例的信息:
package cn.icexmoon.demo.books.book.controller;
@RestController
@RequestMapping("/activiti")
@Api(tags = "Activiti示例接口")
public class ActivityController {
@Autowired
private IActivitiService activitiService;
@Data
@Accessors(chain = true)
private static class StartProcessVO implements IResult {
private String id;
private String name;
private String deploymentId;
private String processDefinitionId;
private String startUserId;
private String processDefinitionName;
public static StartProcessVO newInstance(ProcessInstance pi) {
StartProcessVO vo = new StartProcessVO();
return vo.setId(pi.getId())
.setName(pi.getName())
.setDeploymentId(pi.getDeploymentId())
.setProcessDefinitionId(pi.getProcessDefinitionId())
.setStartUserId(pi.getStartUserId())
.setProcessDefinitionName(pi.getProcessDefinitionName());
}
}
@ApiOperation("启动流程实例")
@PostMapping("/process/start/{key}")
public StartProcessVO startProcess(@ApiParam("流程key") @PathVariable String key) {
ProcessInstance pi = activitiService.startProcessInstance(key);
return StartProcessVO.newInstance(pi);
}
}
接口调用后的返回信息:
{
"success": true,
"msg": "",
"data": {
"id": "30001",
"name": null,
"deploymentId": null,
"processDefinitionId": "Process_0wu4lop:9:25004",
"startUserId": null,
"processDefinitionName": "A test process"
},
"code": 200
}
这里请求的是http://localhost:8081/activiti/process/start/Process_0wu4lop ,实际上Process_0wu4lop 有9个部署,版本号是1~9,可以看到Activiti会选择最高版本号的流程定义来启动一个流程实例。
虽然这里的确可以启动一个流程实例,但其实是无法在数据库或者输出中观察到这个流程执行的,这是因为这个示例流程实际上只有一个普通的task 任务,该任务既不会执行特定程序也不会等待用户操作,所以流程实例启动后就会立即结束,我们不会观察到任何行为。
可以将task 替换为serviceTask ,这种类型的任务可以触发相应的程序执行。具体的方式为先定义一个实现了JavaDelegate 接口的类:
package cn.icexmoon.demo.books.book.entity.task;
public class MyTestTask implements JavaDelegate {
@Override
public void execute(DelegateExecution delegateExecution) {
System.out.println("MyTestTask is executed.");
}
}
修改bpmn 定义:
<?xml version="1.0" encoding="UTF-8"?>
<bpmn:definitions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
<!-- ... -->
xmlns:activiti="http://activiti.org/bpmn"
id="Definitions_1kwvfrz" targetNamespace="http://bpmn.io/schema/bpmn" exporter="bpmn-js (https://demo.bpmn.io)" exporterVersion="9.3.2">
<bpmn:process id="Process_0wu4lop" isExecutable="true" name="A test process">
<bpmn:serviceTask id="Activity_0nt5d38" activiti:exclusive="true" name="approve" activiti:class="cn.icexmoon.demo.books.book.entity.task.MyTestTask">
<bpmn:incoming>Flow_0nv17f1</bpmn:incoming>
<bpmn:outgoing>Flow_1ke9x09</bpmn:outgoing>
</bpmn:serviceTask>
</bpmn:process>
</bpmn:definitions>
这里的关键是通过serviceTask 节点的activiti:class 属性”绑定“任务需要执行的Task 类。Activiti就会在执行流程实例时,在流程执行到这个Task时执行对应的Task类中的execute 方法。
然后按之前做的,重新加载和启动一个Process_0wu4lop 流程,一切都OK的话会在控制台看到MyTestTask is executed. 输出。
下面看一个更复杂点的流程:
<?xml version="1.0" encoding="UTF-8"?>
<bpmn:definitions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI"
xmlns:dc="http://www.omg.org/spec/DD/20100524/DC"
xmlns:di="http://www.omg.org/spec/DD/20100524/DI"
xmlns:activiti="http://activiti.org/bpmn"
id="Definitions_13909a0" targetNamespace="http://bpmn.io/schema/bpmn" exporter="bpmn-js (https://demo.bpmn.io)" exporterVersion="9.3.2">
<bpmn:process id="Process_0hy83oz" isExecutable="true" name="A test process 2">
<bpmn:startEvent id="StartEvent_07jmeqi">
<bpmn:outgoing>Flow_1cydcrq</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:serviceTask id="Activity_1tlvosh" activiti:exclusive="true" name="task1" activiti:class="cn.icexmoon.demo.books.book.entity.task.MyTestTask2">
<bpmn:extensionElements>
<activiti:field name="text1">
<activiti:string><![CDATA[test1]]></activiti:string>
</activiti:field>
</bpmn:extensionElements>
</bpmn:serviceTask>
<bpmn:sequenceFlow id="Flow_1cydcrq" sourceRef="StartEvent_07jmeqi" targetRef="Activity_1tlvosh" />
<bpmn:serviceTask id="Activity_00xge6t" activiti:exclusive="true" name="task2" activiti:class="cn.icexmoon.demo.books.book.entity.task.MyTestTask3">
<bpmn:extensionElements>
<activiti:field name="text2">
<activiti:string><![CDATA[test2]]></activiti:string>
</activiti:field>
</bpmn:extensionElements>
</bpmn:serviceTask>
<bpmn:sequenceFlow id="Flow_1x26529" sourceRef="Activity_1tlvosh" targetRef="Activity_00xge6t" />
<bpmn:endEvent id="Event_0j69tgw">
<bpmn:incoming>Flow_135u817</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="Flow_135u817" sourceRef="Activity_00xge6t" targetRef="Event_0j69tgw" />
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
</bpmndi:BPMNDiagram>
</bpmn:definitions>
这个流程包含两个ServiceTask,并且通过extensionElements 节点添加了Activiti自定义节点,通过该自定义节点我们可以在相应的Task执行时,获取或重写对应的属性。
这里有个奇怪的问题,必须注释掉serviceTask 下的incoming 和outgoing 节点,否则无法正常部署这个流程定义到Activiti,会提示extensionElements 节点定义不正确。
我翻阅了bpmn2官方定义,但并没有发现xsd文件中定义了extensionElements 节点不能与incoming 节点共存这样的东西:
<xsd:element name="flowNode" type="tFlowNode"/>
<xsd:complexType name="tFlowNode" abstract="true">
<xsd:complexContent>
<xsd:extension base="tFlowElement">
<xsd:sequence>
<xsd:element name="incoming" type="xsd:QName" minOccurs="0" maxOccurs="unbounded"/>
<xsd:element name="outgoing" type="xsd:QName" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
<xsd:element name="extensionElements" type="tExtensionElements"/>
<xsd:complexType name="tExtensionElements">
<xsd:sequence>
<xsd:any namespace="##any" processContents="lax" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
</xsd:complexType>
这个问题目前不清楚是Activiti解析的问题还是bpmn定义的问题。
ServiceTask对应的Java类:
@Log4j2
public class MyTestTask2 implements JavaDelegate {
private Expression text1;
@Override
public void execute(DelegateExecution delegateExecution) {
log.info("MyTestTask2 is executed.");
String value = (String) this.text1.getValue(delegateExecution);
log.info("get text1's value:" + value);
delegateExecution.setVariable("text", value);
}
}
@Log4j2
public class MyTestTask3 implements JavaDelegate {
private Expression text2;
@Override
public void execute(DelegateExecution delegateExecution) {
log.info("MyTestTask3 is executed.");
String value = (String) this.text2.getValue(delegateExecution);
log.info("get text2's value:" + value);
String textVal = (String) delegateExecution.getVariable("text");
textVal += value;
delegateExecution.setVariable("text", textVal);
log.info("finally, the text's value is:" + textVal);
}
}
部署并执行后可以看到下面这样的输出:
15:18:39.764 [http-nio-8081-exec-3] INFO c.i.d.b.book.entity.task.MyTestTask2 - MyTestTask2 is executed.
15:18:39.764 [http-nio-8081-exec-3] INFO c.i.d.b.book.entity.task.MyTestTask2 - get text1's value:test1
15:18:39.766 [http-nio-8081-exec-3] INFO c.i.d.b.book.entity.task.MyTestTask3 - MyTestTask3 is executed.
15:18:39.766 [http-nio-8081-exec-3] INFO c.i.d.b.book.entity.task.MyTestTask3 - get text2's value:test2
15:18:39.766 [http-nio-8081-exec-3] INFO c.i.d.b.book.entity.task.MyTestTask3 - finally, the text's value is:test1test2
关于Activiti就先到这里了,到这里只算是对Activiti有一个大概了解,要实际使用还需要尝试将Activiti与项目内的组织架构整合,实现流程审批等才行,有空再尝试。
谢谢阅读。
本篇文章最终的完整示例代码见learn_spring_boot/ch21 (github.com)。
参考资料
|