Activiti(一)
目录
需求
在实际社会生产环境中,我们存在这样的需要,即需要由一个人发起一项活动,这个活动涉及到多个参与者,且可能位于不同的空间中。这样的一个活动就是流程,比如我们常见的请假过程,就是一个流程:由请假人发起请假(填写请假表单),之后由部门领导或老师审批,一直到最终部门审批。
为了更加方便的处理上述过程(自动化处理),流程支持工具出现了,比如我们这里主要介绍的Activiti
。
原理
-
流程自动化的传统做法是给条目加上状态字段,每个角色读取属于自己状态码所对应的条目信息然后进行操作并改变状态码信息。这样带来的问题是,当业务流程发生改变时,代码需要重写。
Activiti
的出现就是为了解决上述问题。 -
在流程定义中,工作流需要规范定义节点(绘制流程图时),如某个节点拥有哪些属性、使用什么图标等,这一套规则依赖于
bpmn
。 -
流程支持工具总的步骤是:
- 先将业务流程图画好(专门的工具【
Activiti
中是Activiti Designer
】) - 将流程图中每个节点的数据读取并放入表中
- 读取表中的第一条记录,处理并删除
- 先将业务流程图画好(专门的工具【
-
原理总结:
- 业务流程图要规范化,要遵守一套标准
- 这个业务流程图实际上是一个
xml
文件 - 读取业务流程图的过程就是解析
xml
文件的过程 - 读取流程图中的一个节点相当于解析一个
xml
节点,并将数据插入到mysql
表中,形成一条记录 - 主要将所有节点都读取并存入
mysql
表中 - 后面只需要读取
mysql
中的记录 - 业务流程的推进,后面即转换为读取表中数据并处理
-
Activiti
软件本身关联到的技术:xml
+dom4j
+mysql
+jdbc
- 流程定义文件
BPMN
实际上是一个XML
文件,需要用到解析工具 - 自动生成表时需要与数据库交互
- 流程定义文件
- 一些术语的全称
-
BPM(Business Process Management)
业务流程管理 -
BPMN(Business Process Model And Notation)
业务流程模型和符号 BPMI(Business Process Management Initiative)
-
使用Activiti
-
需要将
Activiti
整合到项目中 -
实现业务流程建模,即使用
BPMN
实现业务流程图 -
部署业务流程到
Activiti
- 流程定义部署是指将线下定义的流程(用建模工具
BPMN
绘制的流程图和生成的png
流程图片)通过调用activiti
中的相关API
部署到activiti
数据库中。
- 流程定义部署是指将线下定义的流程(用建模工具
-
启动流程实例
-
查询待办任务
-
处理待办任务
-
结束流程
-
针对
Eclipse
和IDEA
有不同的插件整合方式(目的是让集成开发环境支持Activiti
开发) -
在开发前,需要导入相应的
jar
包,一般使用Maven
构建项目,故在pom.xml
文件中写入依赖的坐标即可(参照项目中的actiProj
项目文件) -
引入依赖并配置好数据库等之后,执行测试代码,会生成25张表(视
Activiti
版本不同而不同,这里使用的Activiti
版本是7.0.0.Beta1
) -
25张表的名称有一定的规律
- 均以
act
开头 - 第二部分是表示表的用途的字符标识(用途和流程服务的API对应)
-
act_re_*
: ‘re’表示’repository’;这些表包含了流程定义和流程静态资源(图片、规则等)【共3张】。 -
act_ru_*
: ‘ru’表示’runtime’;这些表是运行时的表,包含流程实例、任务、变量、异步任务等运行中的数据(只在流程实例运行过程中保存这些数据,流程结束时会删除这些数据,兼顾表的大小和速度)【共10张】。 -
act_hi_*
: ‘hi’表示’history’;这些表包含历史数据,比如历史流程实例、变量、任务等【共8张】。 -
act_ge_*
: ‘ge’表示’general’;这些表包含通用数据,用于不同场景下【共2张】。 -
act_evt_log
: 该表包含日志信息。 -
act_procdef_info
: 该表包含流程定义信息。
-
- 均以
Activiti服务架构图
流程引擎配置文件
activiti.cfg.xml
是Activiti
默认的配置文件,该配置文件类似于Spring
的配置文件,在其中主要配置数据源、事务以及Activiti
的配置,如ProcessEngineConfiguration
的配置。下面的事例是一个基本的流程配置文件,定义了数据源和流程引擎。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
">
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/activiti"/>
<property name="username" value="username"/>
<property name="password" value="password"/>
</bean>
<!-- 使用脱机方式创建对象 -->
<bean id="processEngineConfiguration" class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">
<property name="dataSource" ref="dataSource"></property>
<!-- 设置是否自动生成数据表 -->
<property name="databaseSchemaUpdate" value="true"></property>
</bean>
</beans>
ProcessEngineConfiguration
-
流程引擎的配置类;
-
位于
org.activiti.engine
包中; -
通过该类的对象可以创建工作流引擎
ProcessEngine
。 -
创建工作流引擎(
ProcessEngineConfiguration
)通常有两种形式:-
形式一:使用脱机方式创建(不与
Spring
整合)-
使用的类:
StandaloneProcessEngineConfiguration
(在org.activiti.engine.impl.cfg
包中)(如上面的配置文件中就是使用该类创建) -
通常
Activiti
可以单独运行,通过这种方式创建的ProcessEngine
,事务由Activiti
自己处理。 -
使用这种方式的配置文件如下,名称通常固定为
activiti.cfg.xml
,同时bean
名称也固定,可以从ProcessEngineConfiguration
的源代码中看出。public static ProcessEngineConfiguration createProcessEngineConfigurationFromResourceDefault() { return createProcessEngineConfigurationFromResource("activiti.cfg.xml", "processEngineConfiguration"); } public static ProcessEngineConfiguration createProcessEngineConfigurationFromResource(String resource) { return createProcessEngineConfigurationFromResource(resource, "processEngineConfiguration"); } public static ProcessEngineConfiguration createProcessEngineConfigurationFromResource(String resource, String beanName) { return BeansConfigurationHelper.parseProcessEngineConfigurationFromResource(resource, beanName); }
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"> <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"></property> <property name="url" value="jdbc:mysql://localhost:3306/activiti"/> <property name="username" value="username"/> <property name="password" value="password"/> </bean> <!-- 使用脱机方式创建对象 --> <bean id="processEngineConfiguration" class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration"> <property name="dataSource" ref="dataSource"></property> <!-- 设置是否自动生成数据表 --> <property name="databaseSchemaUpdate" value="true"></property> </bean>
-
注意:虽然形式一事务由
Activiti
自己处理,但同时使用到了Spring
的依赖注入来创建实例。
-
-
形式二:通过与
Spring
整合的方式- 使用的类:
SpringProcessEngineConfiguration
(在org.activiti.spring
包中) - 使用整合的方式创建时,配置文件需要是与
Spring
整合的配置文件,名称通常是activiti-spring.cfg.xml
。但事实上上面形式一的配置文件就可以,只不过需要改变里面bean
实例的相关配置。
- 使用的类:
-
-
创建
processEngineConfiguration
-
在业务类中可以通过下面的代码创建
processEngineConfiguration
ProcessEngineConfiguration configuration = ProcessEngineConfiguration.createProcessEngineConfigurationFromResource("activiti.cfg.xml"); // 参数为activiti配置文件名称,通常在类路径下
-
通过上面代码创建
processEngineConfiguration
时,需要在配置文件中存在一个名称为processEngineConfiguration
的bean
,如果不想使用默认的bean
名称,可以使用之前源码中的第三块代码,其中的beanName
在配置文件中和调用方法中需要保持一致。public static ProcessEngineConfiguration createProcessEngineConfigurationFromResource(String resource, String beanName) { return BeansConfigurationHelper.parseProcessEngineConfigurationFromResource(resource, beanName); }
-
ProcessEngine
-
ProcessEngine
的存在跟它的名称一样,是一个引擎,只有存在这个引擎,才能使用这个引擎所在系统的各种服务,即其本质上是一个门面接口。 -
在
ProcessEngine
对象中,提供了调用各种服务的方法,这些服务有RepositoryService
,RuntimeService
,TaskService
,HistoryService
,ManagementService
,DynamicBpmnService
。与上面的架构图相比,Activiti7
中去掉了IdentityService
和FormService
两个服务。 -
获取到相应的服务后,实质上就可以操作生成的对应的数据表(如
HistoryService
服务就可以操作与流程历史数据相关的表act_hi_*
)。 -
创建方式:
-
一般方式:通过
ProcessEngineConfiguration
创建,上面ProcessEngineConfiguration
章节中就是使用这种方式创建的。通过这种方式创建时,灵活性更高,程序员有更多的选择性。 -
简单方式:通过
ProcessEngines
创建,这种创建方式通常需要满足以下两个条件:①Activiti
的配置文件名称为activiti.cfg.xml
;②在配置文件中存在一个id为processEngineConfiguration
的bean
。ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
-
Service(各种服务)的创建
在架构图中的末端,是一些具体的Service API
,如何创建这些服务的对象呢?
如前面所述,ProcessEngine
是一个引擎,提供调用各种服务接口的方法,故当然可以通过ProcessEngine
创建这些Service
对象。
-
Service
是工作流引擎提供用于进行工作流部署、执行、管理的服务接口。具体如下:服务接口 功能 RepositoryService Activiti
的资源管理类RuntimeService Activiti
的流程运行管理类TaskService Activiti
的任务管理类HistoryService Activiti
的历史管理类ManagementService Activiti
的引擎管理类 -
通过
ProcessEngine
创建各种Service
:RepositoryService repositoryService = processEngine.getRepositoryService(); RuntimeService runtimeService = processEngine.getRuntimeService(); DynamicBpmnService dynamicBpmnService = processEngine.getDynamicBpmnService();
-
ProcessEngine
中提供的调用各种Service
的方法:RepositoryService getRepositoryService(); RuntimeService getRuntimeService(); TaskService getTaskService(); HistoryService getHistoryService(); ManagementService getManagementService(); DynamicBpmnService getDynamicBpmnService();
详述各种Service
RepositoryService
-
RepositoryService
是Activiti
中的资源管理类,提供了管理和控制流程发布包和流程定义的操作。具体如下:- 使用工作流建模工具设计的业务流程图需要借助该服务将流程定义文件的内容部署到计算机;
- 查询引擎中的发布包和流程定义;
- 暂停或激活发布包,分别对应全部和特定流程定义;
- 获得多种资源,如包含在发布包里的文件,引擎自动生成的流程图;
- 获得流程定义的
POJO
版本,从而实现Java
解析,而非XML
解析。
RuntimeService
- 是
Activiti
的流程运行管理类,可以获取很多流程执行相关的信息。
TaskService
- 是
Activiti
的任务管理类,可以获取到任务的信息。
HistoryService
- 是
Activiti
的历史管理类,可以查询到流程的历史信息。在执行流程时,引擎会保存诸如流程实例启动时间、任务参与者、完成任务的时间、每个流程实例的执行路径等数据(根据配置确定),这些数据可以通过该服务查询获得。
ManagementService
- 是
Activiti
的引擎管理类,提供对Activiti
流程引擎的管理和维护功能,主要用于Activiti
系统的日常维护。
简单实例
要使用Activiti
处理流程业务,在上述理论部分也提到,首先需要为你的开发环境提供支持开发的各种插件,具体见上面的内容。
在做好准备工作之后,就可以开始项目的开发了。
(1)流程定义
-
在流程的定义中,需要使用到
Activiti-Designer
工具,该工具提供绘制流程图的画板(Palette),在画板中包括下面的结点:-
Connection
:连接 -
Event
:事件 -
Task
:任务 -
Gateway
:网关 -
Container
:容器 -
Boundary event
:边界事件 -
Intermediate event
:中间事件
-
-
需要注意的是,在定义流程的过程中,还需要为目标流程设置一些属性(如流程的ID、流程的名称等),具体界面视集成开发环境的不同而不同。在笔者使用的Eclipse集成开发环境中,需要在
Properties
视图中进行配置,具体打开该视图的步骤是:单击工具栏中的Window
——>选中Show View
选项——>点击Other
子选项——>在弹出的对话框中搜索Properties
并选中回车即可。-
设置整个流程的属性(单击空白部分,即可在
Properties
视图窗口中显示流程的相关属性),主要设置流程的ID和名称 -
设置各个结点的属性(单击流程图中目标结点,在
Properties
视图窗口中显示相关属性),只要设置结点的任务审批人
-
-
在绘制并配置好流程图(定义好流程)之后,会生成
.bpmn
格式的文件。
(2)部署流程定义
-
有了流程图(流程定义)还不行,需要将其部署到工作流引擎
activiti
中。方法如下:-
使用
ProcessEngine
创建RepositoryService
; -
使用
repositoryService
部署已经定义好的流程。代码如下:// 创建ProcessEngine ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); // 得到RepositoryService实例 RepositoryService repositoryService = processEngine.getRepositoryService(); // 进行部署 // 方式一:单个文件分别部署 Deployment deployment = repositoryService.createDeployment() .addClasspathResource("diagram/holiday.bpmn") // bpmn文件 .addClasspathResource("diagram/holiday.png") // 图片文件 .name("请假流程") .deploy(); // 部署 // 方式二:压缩包部署方式(将单个流程定义文件添加到压缩包之后部署)(这种方式便于以后实际的服务器部署) ActivitiDeployment.class.getClass().getClassLoader(); InputStream is = ClassLoader.getSystemResourceAsStream("diagram/holidayBPMN.zip"); ZipInputStream zipInputStream = new ZipInputStream(is); // 进行部署 Deployment deployment = repositoryService.createDeployment() .addZipInputStream(zipInputStream) .name("请假流程").deploy(); // 输出部署信息 System.out.println(deployment.getName()); // 请假流程 System.out.println(deployment.getId()); // 1
-
可以发现,在输出的部署信息中,名称与我们设置的流程名称一致,而流程的ID是“1”,这是因为使用了
MySQL
的主键自动增长策略,观察被操作的数据库表act_re_deployment
表可以得知。
-
-
涉及到的表
-
act_re_deployment
:部署信息(如不是流程的名称、部署时间等) -
act_re_procdef
:流程定义的一些信息(如流程的key等) -
act_ge_bytearray
:流程定义的bpmn
文件和png
文件
-
(3)启动一个流程实例
-
流程定义部署到
activiti
中后,就可以通过工作流管理业务流程了。这种关系类似Java中
的Java类
和对象,光有类不行,还需要通过实例化一个类对象才能使用该类的所有属性和方法。 -
启动一个流程,在该实例中就是发起一个新的请假申请单。发起人(张三)发起一个请假申请单,需要启动一个流程实例;请假申请单发起一个请假单也需要启动一个流程实例。
-
启动方法如下:
-
使用
ProcessEngine
创建RuntimeService
对象实例; -
使用
RuntimeServcie
实例对象根据已经定义好且已经部署的流程定义的key
创建(启动)流程实例。代码如下:// 得到ProcessEngine对象 ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); // 得到RuntimeService对象 RuntimeService runtimeService = processEngine.getRuntimeService(); // 创建流程实例 根据流程定义的key创建实例 ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("holiday"); // 输出实例的相关信息 System.out.println("流程部署ID:" + processInstance.getDeploymentId()); // 流程部署ID:null System.out.println("流程实例ID:" + processInstance.getId()); // 流程实例ID:2501
-
可以发现,在启动流程实例后,可以获得已经启动的流程实例的ID。
-
-
涉及到的表
-
act_hi_actinst
:存储已完成的活动信息,即已经完成了流程图中的哪些结点信息。 -
act_hi_identitylink
:流程中结点关联的参与者信息(即需要处理该结点的人员信息)。 -
act_hi_procinst
:存储流程实例信息,主要关注流程实例ID。 -
act_hi_taskinst
:存储任务实例(谁要完成什么工作)信息。 -
act_ru_execution
:任务执行表。
-
act_ru_identitylink
:运行时的参与者信息。
-
act_ru_task
:存储任务信息。
-
(4)任务查询
-
流程实例启动以后,各个任务的负责人就可以查询自己当前需要处理的任务,查询出来的所有任务都是该用户当前需要处理的待办任务。
-
查询方法如下:
-
首先仍然需要得到
ProcessEngine
对象,并通过该对象实例创建TaskService
对象(用户需要处理的待办结点事项就是任务); -
根据流程定义的
key
和任务参与者信息(流程定义时指定的结点负责人)查询得到该用于当前需要处理的任务。代码如下:// 得到ProcessEngine对象 ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); // 得到TaskService对象 TaskService taskService = processEngine.getTaskService(); // 根据用户信息查询当前需要处理的任务 List<Task> taskList = taskService.createTaskQuery().processDefinitionKey("holiday") .taskAssignee("zhangsan") .list(); // 任务列表内容展示 for (Task task : taskList) { System.out.println("流程实例ID:" + task.getProcessInstanceId()); // 流程实例ID:2501 System.out.println("任务ID:" + task.getId()); // 任务ID:2505 System.out.println("任务负责人:" + task.getAssignee()); // 任务负责人:zhangsan System.out.println("任务名称:" + task.getName()); // 任务名称:填写请假申请单 }
-
-
可以发现,查询任务负责人所要处理的任务信息,主要是查询上面在启动流程实例后的一些表信息。
(5)任务处理
-
任务负责人查询到待办任务,选择任务进行处理,完成任务。
-
该步骤的进行,依赖于上述任务的查询,需要首先查询到当前用户(负责人)需要处理流程中的哪些任务,故可以在正式开发时将查询和处理合并到一起进行。方法如下:
-
根据参与者信息和流程定义的
key
查询到需要处理的任务ID; -
将对应ID的任务处理,调用
TaskService
对象的complete
方法即可。代码如下:// 得到ProcessEngine对象 ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); // 得到TaskService对象 TaskService taskService = processEngine.getTaskService(); // 完成任务 List<Task> taskList = taskService.createTaskQuery().processDefinitionKey("holiday") .taskAssignee("zhangsan") .list(); for (Task task : taskList) { taskService.complete(task.getId()); System.out.println("当前完成的任务ID是:" + task.getId()); // 当前完成的任务ID是:2505 }
-
-
可以发现,当当前用户只有一条需要完成的任务时,只输出一条任务信息。
-
涉及到的表
-
act_hi_actinst
:存储已完成的任务结点信息。可以发现,在结点一被处理以后,新的结点被加入进来。 -
act_hi_identitylink
:新的任务结点加入进来,加入新的任务负责人信息。 -
act_hi_taskinst
:新的任务实例被加入进来,同时前一结点的任务加入完成时间。 -
act_ru_execution
:执行新的结点任务,更新当前流程任务结点ID。 -
act_ru_identitylink
:运行时任务结点的参与者信息,与历史表中的参与者信息一致。 -
act_ru_task
:当前运行的任务,保证表中存储的是当前正在运行的结点任务。
-
-
剩下的任务结点需要改变完成流程任务代码中的参与者信息,待完成所有任务后,上述表中以
act_ru
开头的表中的信息也会随之被删除,以act_hi
开头的表中的信息会被补全。
上面这个流程示例就是一个简单的请假流程,从流程的定义、流程的部署、流程实例的启动以及流程各结点任务的完成。需要说明的是,上面的这个示例是一个单独运行的Activiti
流程,没有真正整合到带有流程的Web
项目中。