IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> 从零开始 Spring Boot 21:Activiti -> 正文阅读

[Java知识库]从零开始 Spring Boot 21:Activiti

从零开始 Spring Boot 21:Activiti

spring boot

图源:简书 (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包:

image-20220830112719171

用解压工具直接解压该jar包,可以在db目录下找到这样几个目录:

image-20220830112853698

这几个目录分别对应创建表、删除表、更新表等的DLL SQL语句,这里我们只需要用到creat。

image-20220830113019824

创建语句是按照数据库分类的,因为虽然SQL语法是通用的,但是因为数据库类型和版本的不同,在某些特性上会有差异导致SQL语句不会完全相同。

这里按照项目自身使用的数据库类型选择即可,我这里是MySQL。

之后就是将这些建表的DDL导入数据库了,选择自己顺手的工具即可,我这里是使用sqlyog。

其实Activiti可以在启动后扫描数据库,如果缺少相应的表结构会自动创建,但是这里我整合后并不能自动创建表结构,进而因为缺少表结构无法启动应用,不知道是不是配置的问题。

此外,很多商业部署因为安全方面考虑,生产环境是没有DDL语句的数据库执行权限的,因此需要通过上述方式提取DDL语句后提交给DBA来完成建表工作。、

开始

制作流程

Activiti使用的是由BPMN2.0标准定义的流程图,有很多工具都可以制作该标准的流程图,使用最多的是在IDE中集成的各种BPMN相关插件。不过我这里使用Activiti官方提供的一个在线工具:

使用该工具可以绘制一个最简单的流程图:

image-20220901154855783

该流程图仅包含3个最简单的元素:

  • StartEvent,流程的开始。
  • Task,流程中需要执行的动作,可能是用户审批或者某些代码完成的自动化工作。
  • EndEvent,流程结束。

用在线工具绘制好后可以通过左下角菜单导出BMPN2.0文件,我们需要将这个文件保存到Spring Boot的静态资源目录下的processes目录下:

image-20220901155402399

实际上该文件是一个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,对应流程图的StartEvent
  • endEvent,对应流程图的EndEvent
  • task,对应流程图中的Task
  • sequenceFlow,对应流程图中连接Event和Task的线段

通过sequenceFlowsourceRef属性和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_rerepository(仓库)的缩写,即流程相关的静态信息,包括流程定义和流程资源(图片,规则)等。
  • ACT_RU_ruruntime(运行时)的缩写,这些表包含流程在运行时产生的数据,包括(流程实例、用户任务、变量、定时任务等)。Activiti会在流程实例启动时存储相关数据,并在流程实例结束时移除这些数据,这样可以保持一个精简的表数据规模,以维持高效的数据库性能。
  • ACT_ID_ididentity的缩写,这些表包含了相关的ID信息,包括用户、用户组等。
  • ACT_HI_hihistory的缩写,这些表包含了历史数据,包括运行过的流程实例、变量、任务等。
  • ACT_GE_gegeneral(通用)的缩写,这些表包含了一般性的数据。

时间检查表就会发现下面这些表产生了数据:

act_ge_bytearray

image-20220901163823463

显然这个表保存的是从processes加载的BPMN文件信息。

act_re_deployment

image-20220901164023463

这个包含的是我们部署流程时的信息,其中SpringAutoDeployment是自动部署时产生的部署信息。

act_re_procdef

image-20220901164428478

这个表包含了流程定义,其中HAS_START_FORM_KEY_字段表示流程是否启动。

当然实际使用中我们并不需要每次在项目启动时部署流程,我们可以将流程部署进行封装:

public interface IActivitiService {
    /**
     * 部署流程
     *
     * @param classPathResource 资源路径,如processes/diagram.bpmn
     * @param key               流程key,如Process_0wu4lop
     * @param name              流程名称,如示例流程
     */
    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();
    }
}

之后就可以通过接口调试工具部署流程了:

image-20220908115421078

流程定义

流程部署后,会以流程定义(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:12505oneTaskProcess: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:incoming>Flow_1cydcrq</bpmn:incoming> -->
<!--       <bpmn:outgoing>Flow_1x26529</bpmn:outgoing> -->
      <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:incoming>Flow_1x26529</bpmn:incoming> -->
<!--       <bpmn:outgoing>Flow_135u817</bpmn:outgoing> -->
      <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下的incomingoutgoing节点,否则无法正常部署这个流程定义到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)

参考资料

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-09-13 10:59:34  更:2022-09-13 11:00:33 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 12:16:48-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码