上一讲我们画了一个简单的请假的业务流程图,这一讲我们把它部署到activiti7中,并将它执行完毕。先简单介绍下Activiti7工作流引擎。
一、Activiti7简介
1、Activiti7是什么?
Activiti7只是对BPMN2.0规范实现的一个java框架而已,他是一个工作流程控制和管理框架,就是来处理系统中的业务流程的,对整个业务系统起到辅助和支持作用。一般有两种存在方式,一种是和业务代码耦合在一块,另一种是依靠activiti7做成单独的微服务,实现功能的复用,成为真正的工作流“引擎”。
之所以将他称之为工作流引擎,我想有两个原因,第一activiti7是用来处理业务流程的,这些业务流程统称为工作流;第二,引擎的原因是他对代码对了高度封装,完全屏蔽掉了底层,暴露出很简单的操作API,只需要简单操作(实则底层做了大量工作)就能完成复杂的业务流程处理,就像发动机引擎一样,内部实现很复杂,但是用户使用起来确很简单,所以称之为引擎。
2、为什么要用它?或者说他解决了什么问题?
1)工作流引擎本身的目的就是为了辅助业务系统,处理复杂的业务流程,实现流程自动化处理,减轻开发人员的负担,提高企业运作效率,为企业赋能。对于开发人员来说,无论多么复杂的业务流程,只要是用BPMN2.0规范画的业务流程图,用activiti7都可以轻松应对,甚至有时当业务流程发生改变后,都不需要改原来的代码。这一切都源于BPMN2.0规范和acitivit7代码的高度封装屏蔽了底层的实现。
2)什么场景下适合使用工作流引擎?
- 如果要做的是事需要多个节点参与,即需要走一个流程的时候,可以使用工作流引擎,因为他就是专门对流程进行处理、控制和管理的。
- 如果业务流程可能会发生改变,要用工作流引擎,可以做到基本不用修改原来的代码。就从这一点来说,开发人员都要选择使用它。
- 如果业务流程很复杂,要用工作流引擎,可以化繁为简,使代码变得简单。
3、要学习他的什么?
愚以为学习activiti7要重点关注以下几点,就算是基本完成activiti7的学习了:
1)熟悉 BPMN2.0规范并会画图。
2)熟悉activiti7的工作流程。
3)熟悉activiti7的API。
4)熟悉activiti7生成的数据表及其字段的含义。
5)熟悉activiti7的基本操作流程。
①、画业务流程图——对业务流程进行建模
②、部署流程图给activiti(解析xml,并保存到数据库中)
③、启动流程
④、查询待办任务
⑤、办理任务(④、⑤可能会循环好几遍)
⑥、流程结束
4、activiti7和activiti6的区别:
大部分的功能都是一样的,不同点是:activiti6是28张数据表,activiti7是25张,少了用户和组的三张表。相应的服务接口也少了俩:IdentityService和FormService。另外activiti7中对activiti6的API再次进行了封装,使得用户操作代码更简洁了,并新增加了分布式和云部署的功能,核心没变。
5、支持的数据库:
默认内置的内存数据库h2,mysql,oracle,postGreSql,db2
6、操作api架构图
前面也说了,activiti7和activiti6相比核心没变。一个默认在类路径下的配置文件activiti.cfg.xml,和配置文件对应的配置类ProcessEngineConfiguration,配置文件用于将配置类的属性信息在外部配置,配置类用于将配置文件的内容封装成java代码。一个核心流程引擎对象ProcessEngine,根据配置类的信息来创建和初始化。由ProcessEngine对象的getXXXService方法获取各种操作service对象,结构很清晰。
7、整体架构图
中间有一个命令拦截器CommandInterceptor,用于对上层的操作拦截并转化为底层可识别的命令,作用有点类似于业务网关,或者语言解释器。
8、用activiti开发工作流的一个基本开发模式
功能复用:对于像查询待办任务、已办任务、抄送我的、我发起的流程、流程部署、流程挂起与激活、生成流程图等这样的可以复用的功能,使用一套代码即可,提供统一的接口或service。
功能不复用:但是对于启动流程、完成任务这样的功能,因为每个流程的参数或流程变量不一样,并且任务完成之后做的回调事情也不一样,如果不能复用,则每一个流程都单独开发这样的功能。
目前采用的方式是:对于启动流程、完成任务抽象出一个interface,让不同的流程service实现各自不同的操作;创建一个简单工厂,根据流程类型实例化不同的流程service;对外只暴露出启动流程和完成任务两个接口,前端传递不同的流程类型,执行不同的service操作。
数据表扩展:如果activiti提供的数据表无法满足业务需求,可以建立关联表,辅助业务运行。
这样基本可以实现,即使当流程发生改动的时候,也只需要对启动流程、完成任务这样单独的功能,改动很少的代码就可以应对改动了。如果流程图是一条直线,删除其中的几个节点后,甚至都不需要改动代码。
二、Activiti7的HelloWorld
1、环境准备
由于是helloworld,本次就用maven+单元测试的方式实现,稍后会有activiti7与spring、springBoot的整合。
1、pom.xml中添加依赖
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<slf4j.version>1.6.6</slf4j.version>
<log4j.version>1.2.12</log4j.version>
</properties>
<dependencies>
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-engine</artifactId>
<version>7.0.0.Beta1</version>
</dependency>
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-spring</artifactId>
<version>7.0.0.Beta1</version>
</dependency>
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-bpmn-model</artifactId>
<version>7.0.0.Beta1</version>
</dependency>
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-bpmn-converter</artifactId>
<version>7.0.0.Beta1</version>
</dependency>
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-json-converter</artifactId>
<version>7.0.0.Beta1</version>
</dependency>
<dependency>
<groupId>org.activiti.cloud</groupId>
<artifactId>activiti-cloud-services-api</artifactId>
<version>7.0.0.Beta1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.40</version>
</dependency>
<!-- log start -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>${log4j.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>${slf4j.version}</version>
</dependency>
<!-- log end -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.5</version>
</dependency>
<!--数据源暂时用dbcp-->
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>1.4</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
</dependencies>
2、类路径下添加activiti.cfg.xml和log4j.properties文件
activiti.cfg.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--dbcp数据源-->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/activiti7_study"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</bean>
<!--配置Activiti的ProcessEngineConfiguration对象,因为没有跟spring整合,所以这里使用单例的。-->
<bean id="processEngineConfiguration" class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">
<!--注入数据源-->
<property name="dataSource" ref="dataSource"/>
<!--指定数据表生成策略,该策略是,若数据表不存在则创建,存在则更新-->
<property name="databaseSchemaUpdate" value="true"/>
</bean>
</beans>
log4j.properties:
log4j.rootCategory=debug, CONSOLE
# Set the enterprise logger category to FATAL and its only appender to CONSOLE.
log4j.logger.org.apache.axis.enterprise=FATAL, CONSOLE
# CONSOLE is set to be a ConsoleAppender using a PatternLayout.
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern=%d{ISO8601} %-6r [%15.15t] %-5p %30.30c %x - %m
3、代码测试
确保类路径下有个processes文件夹,并将上一讲的holiday.bpmn文件放入其中。
1)流程部署代码
public class Activiti7HelloWorld {
static ProcessEngine processEngine = null;
static {
/**
* 初始化流程引擎对象,将会根据配置创建25数据表(创建了索引和外键)
*/
processEngine = ProcessEngines.getDefaultProcessEngine();
}
/**
* 流程部署
* 工作中常用zip包上传的方式来部署流程,zip包里面一个.bpmn文件,一个.png的文件,activiti会自动的解压缩,解析并放到数据表中
*/
@Test
public void processDeployment(){
RepositoryService repositoryService = processEngine.getRepositoryService();
Deployment deployment = repositoryService.createDeployment()
.addClasspathResource("processes/holiday.bpmn")
.addClasspathResource("processes/holiday.png")
.name("请假流程测试")
.deploy();
System.out.println(deployment.getId());//2501,activit生成的ID
System.out.println(deployment.getName());//请假流程
System.out.println(deployment.getKey());//null
System.out.println(deployment.getCategory());//null
System.out.println(deployment.getDeploymentTime());
System.out.println(deployment.getTenantId());//null
/*
受影响的表:3个
act_re_deployment:添加一条流程部署记录
act_re_procdef::添加一条流程定义记录,一个流程定义记录和一个流程图一一对应,流程定义记录的key就是流程图的id
act_ge_bytearray:blob形式保存部署的资源
*/
/*
流程部署意味着两件事:
1、将流程定义文件放到activiti引擎配置的持久化存储中,以便activiti引擎重启时能再次读取部署的流程。
2、解析bpmn文件并转化为activiti内存中的对象模型,然后就能使用acticiti提供的api对持久化的数据进行操作。
*/
}
}
2)启动流程代码
/**
* 启动一个流程实例
*/
@Test
public void startProcessExecution(){
String businessKey = "1";
RuntimeService runtimeService = processEngine.getRuntimeService();
//启动该流程实例时,附带一些附加的数据
Map<String,Object> variables = new HashMap<>();
variables.put("variable01","aa");
variables.put("variable02","bb");
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("holiday",businessKey,variables);
//可以设置流程实例的名字
runtimeService.setProcessInstanceName(processInstance.getId(),"这是一个流程实例的名字,准备给审批人看的!");
System.out.println("流程实例的ID:"+processInstance.getId());//5001
System.out.println("流程实例的名字:"+processInstance.getName());
System.out.println("流程实例的ProcessInstanceId:"+processInstance.getProcessInstanceId());//5001
System.out.println("所属流程部署的ID:"+processInstance.getDeploymentId());//null
System.out.println("所属流程定义的信息:流程定义ID:"+processInstance.getProcessDefinitionId()+",流程定义key:"+processInstance.getProcessDefinitionKey()+",流程定义name:"+processInstance.getProcessDefinitionName()+",流程定义的version:"+processInstance.getProcessDefinitionVersion());//流程定义ID:holiday:1:2504,流程定义key:holiday,流程定义name:测试流程,流程定义的version:1
System.out.println("流程实例的附属变量数据:");
for (Map.Entry<String, Object> entry : variables.entrySet()) {
System.out.println("\t"+entry.getKey()+":"+entry.getValue());
}
System.out.println("该流程是否被挂起:"+processInstance.isSuspended()+"该流程是否已结束:"+processInstance.isEnded());//false,false
System.out.println("该流程的businessKey:"+processInstance.getBusinessKey());//null
/*
受影响的表:9个
act_ru_execution:添加了一个流程实例记录和该流程实例记录下的一个执行实例记录,注意流程实例和执行实例的区别。
act_ru_variable:添加该流程实例的变量,如果流程实例启动时附带了流程变量的话。
act_ru_task:添加了该流程实例的一个待办任务——>填写请假单
act_ru_identitylink:添加了一条身份记录,该流程实例的当前办理人变成了——>zhangsan
act_hi_actinst:历史活跃的实例节点添加了两条《开始》和《填写请假单》,有相同的流程实例ID
act_hi_procinst:历史流程实例添加了一条记录
act_hi_taskinst:历史任务里面添加了一个填写请假单记录
act_hi_varinst:历史流程变量添加了两条记录aa和bb
act_ge_property:next.dbid字段值 变成 7501
*/
}
3)查询待办任务
/**
* 查询某人的待办任务列表
*/
@Test
public void selectTaskListByAssignee(){
TaskService taskService = processEngine.getTaskService();
List<Task> taskList = taskService.createTaskQuery()
.processDefinitionKey("holiday")
.taskAssignee("zhangsan")
.list();
for (Task task : taskList) {
System.out.println("任务ID:"+task.getId());//5007
System.out.println("任务所属执行实例ID:"+task.getExecutionId());//5007
System.out.println("任务所属流程实例ID:"+task.getProcessInstanceId());//5001
System.out.println("任务所属流程定义ID:"+task.getProcessDefinitionId());//holiday:1:2504
System.out.println("任务的办理人assignee:"+task.getAssignee()+",任务的owner:"+task.getOwner());//zhangsan,null
System.out.println("任务的名字:"+task.getName());//填写请假单
System.out.println("任务的key:"+task.getTaskDefinitionKey());//_3,是流程图中的任务节点的ID
System.out.println("任务的领取时间:"+task.getClaimTime());//null
}
/*
数据来源表:
act_ru_task:
*/
}
4)办理待办任务
/**
* 办理任务
* activiti内部工作:根据taskId查询act_ru_task表任务并删除,然后从流程部署的xml文件中读取下一个任务,
* 判断是否是结束节点,若不是则读取该节点放入数据表中;若是做一些数据表处理(删除该流程实例所有ru_*表中的数据,在历史数据表中做记录),然后结束
*
* 值得注意的是:当一个流程实例执行完了,ru_*表中所有该实例相关数据会被全部删除,让运行时表足够小以保证数据库操作的效率。
*/
@Test
public void handleTask(){
//这个taskId就是上一步查询出来任务ID
String taskId = "15007";
TaskService taskService = processEngine.getTaskService();
//给该任务附件一些数据
Map<String,Object> variables = new HashMap<>();
variables.put("variables03","cc");
variables.put("variables04","dd");
taskService.complete(taskId,variables);
System.out.println("当前任务完成!");
/*
受影响的表:8个
act_ru_task:《填写请假单任务》被删除,新添加了一个《部门经理》任务,即流程开始自动往下走了
act_ru_variable:又增加了两条记录cc和dd
act_ru_identitylink:运行时身份表中,增加了lisi这条记录
act_hi_varinst:历史变量表中又增加了两条记录《cc和dd》
act_hi_taskinst:历史任务表中新添加了一个《部门经理》任务
act_hi_identitylink:历史身份表中增加了《lisi》这条记录
act_hi_actinst:历史活跃的实例节点表中增加了一个《部门经理》记录
act_ge_property:next.dbid的值变成了10001
*/
}
一次执行四个单元测试,观察数据表及数据字段值的变化。然后3)和4)步骤循环三次 ,即根据名字查询zhangsan、lisi、wangwu的待办任务,然后办理他们各自的任务,最后观察数据表的变化。
三、activiti7的工作原理
先看一张工作原理图:
解释:
activiti7的作用就是读图并执行,读上一步画好的bpmn图(bpmn图,外表看着是一个流程图,内部其实是一个xml文件,就好比网页的内部是html和css一样),这个图符合BPMN2.0规范。当流程图被部署后,保存到数据表中,启动流程时,将bpmn图加载到内存中,并解析其内部的xml文件,并将下一个节点的数据保存到流程实例表中,等待被办理,当前节点办理完后删除数据表中的数据,然后读取下一个节点的数据再保存到流程实例表中,以此类推。如果遇到的排他网关、并行网关之类的,因为这些都符合BPMN2.0规范,所以工作流引擎的代码内部,就可以照着这个规范,进行判断处理,执行流程,以实现流程的自动化,根本原因还是有一个好用的规范在里面。这样对于activiti7来说,他就有个确定的东西可以依靠了,虽然具体的业务流程不是确定的,但是使用的规范却是确定的。
引发的思考:
我觉得这个设计思想是很不错,通过几个确定的简单的标记,组合成各种各样的情况,只要使用这个bpmn规范,无论用户怎样画图,都逃不过组合的情况,以此对不明白的人称之为自动化、智能化,其实就是把所有的情况都考虑到了。这也和机器学习、人工智能为什么要进行大量的“数据训练”后,才能更精准,就是因为将几乎所有的情况都想到了,然后利用硬件的高性能,迅速分析做出反应。
总的来说:
activiti7就是将上述过程中如解析xml文件、生成数据表、对数据进行增删改查等操作,进行了代码的层层封装,隐藏了实现细节,对外暴露出了好用的API,我们只需要知道这些API怎么使用的,就可以愉快的使用activiti7处理复杂的不确定的业务流程了。
四、后记
以上只是个activiti7的helloworld,并不能运用到生产中,旨在了解activiti7的工作流程和操作流程,以及基本的api使用。下面会对这个helloworld进行改进,介绍activiti7的API中较实用的功能,这些功能将来会在生产环境中使用。
上一篇:
下一篇: