HelloWorld已经成为“入门小实例”的代名词了,上节内容我们通过Activiti的官方实例演示了在某一业务流程中引入Activiti流程引擎带来的便利,当然了,官方提供的app是让我们体会其运行过程的,而我们自己要做的就是应用Activiti框架,构建适合我们自己的应用。我们本节内容就来学Activiti的入门实例。
一、Activiti框架结构
Activiti流程引擎的核心在于其提供的七大服务接口,大家可以通过从字面意思大概的了解下这七大服务组件的作用,这个我们今后会详解。从图中可以看出,这七大服务组件均由ProcessEngine创建,而ProcessEngine由其专门的配置类对象通过解析配置文件来创建 。所以,要想使用七大服务组件,首先需要创建ProcessEngine,要想创建ProcessEngine,首先要创建ProcessEngineConfiguration,要想创建ProcessEngineConfiguration,首先需要编写activiti.cfg.xml配置文件。下面我们就开始我们的入门之旅。
二、Activiti入门实例
1、环境搭建
我们首先创建一个普通的maven项目,目录结构如下:
1、1导入Activiti的核心包以及其依赖包
笔者采用maven坐标的方式将依赖包导入项目,我们给出具体的依赖。主要的依赖包括spring、mysql、mybatis、log4j日志包以及druid数据库连接池。
<!-- 配置版本 -->
<properties>
<spring.version>4.3.17.RELEASE</spring.version>
<mysql.version>5.1.47</mysql.version>
<activiti.version>6.0.0</activiti.version>
<mybatis.version>3.4.6</mybatis.version>
<log4j.version>1.2.17</log4j.version>
<druid.version>1.1.21</druid.version>
</properties>
<dependencies>
<!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>${druid.version}</version>
</dependency>
<!-- activiti的依赖 -->
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-engine</artifactId>
<version>${activiti.version}</version>
</dependency>
<!-- ssm集成的时候使用 -->
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-spring</artifactId>
<version>${activiti.version}</version>
</dependency>
<!-- mysql驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>${mybatis.version}</version>
</dependency>
<!-- log4j -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>${log4j.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<plugins>
<!-- 配置编译的jdk版本 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.7.0</version>
<configuration>
<!-- 指定source和target的版本 -->
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
1、2编写log4j.properties日志配置文件
在resources目录下我们创建log4j.properties配置文件,文件内容我们直接给出:
log4j.rootLogger=INFO, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern= %d{hh:mm:ss,SSS} [%t] %-5p %c %x - %m%n
log4j.logger.com.codahale.metrics=WARN
log4j.logger.com.ryantenney=WARN
log4j.logger.com.zaxxer=WARN
log4j.logger.org.apache=WARN
log4j.logger.org.hibernate=WARN
log4j.logger.org.hibernate.engine.internal=WARN
log4j.logger.org.hibernate.validator=WARN
log4j.logger.org.springframework=WARN
log4j.logger.org.springframework.web=WARN
log4j.logger.org.springframework.security=WARN
1、3创建mysql数据库
在mysql中创建一个数据库,名字可以随便取,但是要记得与稍后要配置的activiti.cfg.xml配置文件中的数据库名称要一致,此处笔者取名activitistudy。至此,环境搭建完毕。下面开始正式进入Activiti的应用
2、流程引擎的创建
我们顺着这样的思路activiti.cfg.xml->ProcessEngineConfiguration->ProcessEngine->服务组件来进行开发。
2、1 activiti.cfg.xml文件的配置
在resources目录下我们创建activiti.cfg.xml配置文件。我们先给出具体的配置然后再解释
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
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
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">
<!--数据源-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="url" value="jdbc:mysql://localhost:3306/activitistudy?useUnicode=true&characterEncoding=utf8"/>
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="username" value="root"/>
<property name="password" value="ilovemcf"/>
</bean>
<!--事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--processEngineConfiguration配置-->
<bean id="processEngineConfiguration" class="org.activiti.spring.SpringProcessEngineConfiguration">
<property name="dataSource" ref="dataSource"/>
<property name="transactionManager" ref="transactionManager"/>
<property name="databaseSchemaUpdate" value="true"></property>
</bean>
</beans>
在以上配置文件中,我们配置了数据源,并指定了数据库的连接属性信息,其次我们配置了事务管理器,并将数据源bean注入到事务管理器中。最后配置了processEngineConfiguration,并将数据源属性以及事务管理器注入到processEngineConfiguration中。此外仍有一个属性需要引起大家的注意:
<property name="databaseSchemaUpdate" value="true"></property>
databaseSchemaUpdate直面意思意为数据库模式的更新策略,也就是当我们要创建一个流程引擎时,我们要对当前数据库进行怎么样的一个操作,该属性有四个枚举值,此处了解即可
flase: 默认值。activiti在启动时,会对比数据库表中保存的版本,如果没有表或者版本不匹配,将抛出异常。
true: activiti会对数据库中所有表进行更新操作。如果表不存在,则自动创建。
create-drop: 在activiti启动时创建表,在关闭时删除表(必须手动关闭引擎,才能删除表)。
drop-create: 在activiti启动时删除原来的旧表,然后在创建新表(不需要手动关闭引擎)。
此处我们填写true
2、2准备业务流程模型文件
还记得我们上节课在Activiti Modeler里绘制的那个业务流程图吗,我们将其下载到本地,是一个名称为studentLeaveProcess.bpmn20.xml的文件,我们将该文件放到resources文件夹下。如果是用eclipse插件画流程图的同学,可以把.bpmn结尾的文件或是.bpmn文件以及图片文件打包压缩放到resources文件夹下。
2、3创建流程引擎
我们在Java源文件目录下创建一个包以及测试类。通过调用如下代码就可以创建Activiti的一个流程引擎:
@Test
public void createProcessEngine(){
ProcessEngineConfiguration processEngineConfiguration = ProcessEngineConfiguration.createProcessEngineConfigurationFromResourceDefault();
ProcessEngine processEngine = processEngineConfiguration.buildProcessEngine();
}
ProcessEngineConfiguration提供了多个重载的静态方法来满足通过不同的方式创建ProcessEngineConfiguration,createProcessEngineConfigurationFromResourceDefault()方法会自动加载resources文件夹下名称为activiti.cfg.xml的配置文件来创建ProcessEngineConfiguration。ProcessEngineConfiguration创建后,即可调用buildProcessEngine()方法创建流程引擎。流程引擎创建后,在控制台上我们是看不出任何的变化的。我们将目光转到我们自定义的mysql数据库activitistudy中,刷新一下数据库后你会惊讶的发现曾经空空如野的数据库,现在突然冒出了28张表。除了act_ge_property表外,别的表只有结构没有数据。其实这些表里存储的就是Activiti的某一流程在运行前、运行时、运行后的一些信息,因为我们目前只创建了一个流程引擎,没有任何流程在执行,肯定是空的表,而act_ge_property存储的仅仅是一些版本信息。就好比你仅仅是造了一个汽车的发动机,没把它安装到任何一台车上,车上的行车记录仪,迈速表肯定是没有信息的。
我们大概了解一下这些表的作用:
- act_re_*: 'RE’表示repository。 这个前缀的表包含了流程定义和流程静态资源(图片,规则,等等)。
- act_ru_*: 'RU’表示runtime。 这些运行时的表,包含流程实例,任务,变量,异步任务,等运行中的数据。 Activiti只在流程实例执行过程中保存这些数据,在流程结束时就会删除这些记录。
- act_id_*: 'ID’表示identity。 这些表包含身份信息,比如用户,组等等。
- act_hi_*: 'HI’表示history。 这些表包含历史数据,比如历史流程实例,变量,任务等等。
- act_ge_*: 通用数据,用于不同场景下,如存放资源文件
2、4创建服务组件
@Test
public void createService(){
//另一种创建ProcessEngine的方式
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//创建流程存储服务组件,实现对流程的部署
RepositoryService repositoryService = processEngine.getRepositoryService();
//创建运行时的服务组件
RuntimeService runtimeService = processEngine.getRuntimeService();
//创建任务服务组件
TaskService taskService = processEngine.getTaskService();
}
代码中我们给出了另一种创建ProcessEngine的方式,ProcessEngine创建后,即可调用get*方法获取对应的服务组件。
2、5使用RepositoryService 部署流程文件
一个业务流程/业务模式只有部署到Activiti中才可以被Activiti驱动,就比如我们上节绘制完流程图后需要将流程图予App进行绑定才可以被使用。而在当前项目中,我们仅仅是将流程图文件studentLeaveProcess.bpmn20.xml放到了resources文件夹下,未做任何操作。Activiti的RepositoryService 服务组件就是用来完成流程文件的部署、删除、挂机等操作。我们通过如下代码来演示:
@Test
public void deployProcess(){
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
//创建流程存储服务组件,实现对流程的部署
RepositoryService repositoryService = processEngine.getRepositoryService();
Deployment deployment = repositoryService.createDeployment()
.addClasspathResource("studentLeaveProcess.bpmn20.xml")
.enableDuplicateFiltering()//过滤重复部署
.deploy();
System.out.println(deployment.getId());
System.out.println(deployment.getDeploymentTime());
}
Activiti的接口允许我们以链式编程的方式进行调用,最终返回一个Deployment对象,该对象封装了我们流程的部署信息,代码中我们在控制台输出了部署完毕的id以及部署时间。同时我们发现act_re_deployment表中也有了信息,而且表中的信息与Deployment的属性是对应的,说明Deployment与act_re_deployment存在一种映射关系。流程部署完毕后,那么该业务流程的信息存放在了哪里呢,答案是act_re_procdef(流程定义)表中,该表中存放了我们业务流程的定义信息。任何一条流程实例的启动都是基于act_re_procdef表中的流程定义信息进行启动。
流程的部署是系统管理员的职责。当流程部署完毕后,用户就可以启动该流程的一个实例,发起一次请假的审批。
2、6使用RuntimeService启动一条流程实例
@Test
public void startProcess(){
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
RuntimeService runtimeService = processEngine.getRuntimeService();
// ProcessInstance processInstance = runtimeService.startProcessInstanceById("studentLeaveProcess:1:4");
ProcessInstance processInstance1 = runtimeService.startProcessInstanceByKey("studentLeaveProcess");
String id = processInstance1.getId();
System.out.println(id);
}
我们通过调用RuntimeService的多个重载方法中的其中一个就可以启动一条流程实例,发起一次请假审批。本例中调用startProcessInstanceByKey,在代码中注释部分startProcessInstanceById同样可以启动一条流程实例。区别这两种方式的不同前,我们首先看下act_re_procdef表
启动某一条流程实例需要用到act_re_procdef表中的信息,ID_字段中流程定义的id在流程部署时可能产生变化,如果我们采用startProcessInstanceById方法启动实例,需要到数据库中查看流程定义id才可以启动。而KEY_字段与我们绘制流程图时定义的Key一直,不会因为对流程部署产生变化,所以笔者采用startProcessInstanceByKey启动一条流程实例。当启动一条流程实例后,就好比汽车发送机驱动汽车开始走了,行车记录仪、迈速表、油量表等都开始运作了。Activiti的这28张表中的某些表相互配合便开始了对当前流程实例运行状态的记录了。我们来详细看下这些表是如何记录当前流程实例的运行过程的。
我们通过绘制的业务模型图知道,流程实例启动后,下一个执行的节点是【学生】请假申请,既然数据库表中act_ru_*开头的表都是记录正在运行的流程实例的情况,我们就从act_ru_*表中的act_ru_execution开始看起,先看数据库表的变化:从什么都没有变成了如下情况
【act_ru_execution】表中的信息用来表示当前流程实例的行进位置。为什么我们启动了一条流程实例,而表中有两条信息呢,我们着重第一条信息和第二条信息的异同
相同:PROC_INST_ID_、PROC_DEF_ID_、ROOT_PROC_INST_ID_、IS_ACTIVE_
相同的流程定义id与流程实例id表示这两条信息都是对于同一条流程实例的状态记录。
不同:ID_,数据库主键不同肯定是理所当然的,不同就有问题了;PARENT_ID_,第一条没有,而第二条的PARENT_ID_(父id)为第一条的ID_,说明第二条记录是第一条的子执行流;ACT_ID_,第一条没有,第二条为studentLeaveApply,用来表示当前流程实例进行到了学生申请这个活动节点,studentLeaveApply为我们定义的用户任务这个活动的id。由此我们得出结论,第一条信息仅仅起到一个指示的作用,真正记录当前流程实例进行位置的是第二条信息,也就是IS_SCOPE_=0这条信息,在流程实例的进行过程中,第一条信息,也就是IS_SCOPE_=1的信息不会变,变的只是第二条信息,也就是IS_SCOPE_=0这条信息,当流程实例结束后,这两条信息,全部在当前表中消失。
【act_ru_task】主要用来存储任务数据,比如当启动流程实例后,下一个节点是用户任务,那么act_ru_task表中就存储了当前的任务信息,来看数据库
act_ru_task表中的EXECUTION_ID_、PROC_INST_ID_对应了当前的执行实例id与流程实例id,NAME_属性表示当前的任务名称,TASK_DEF_KEY_是我们当初绘制业务流程图时定义的。ASSIGNEE_表示当前任务的办理人。我们暂时就梳理这两个表中的信息。其他表的信息,我们今后会有详述。
2、6使用TaskService完成当前任务
我们先来看如何以最粗暴的方式完成当前任务:
@Test
public void completeTask(){
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
TaskService taskService = processEngine.getTaskService();
taskService.complete("2505");
}
通过taskService.complete(String taskId);直接传入任务id号就可以完成当前任务,使业务流程转向下一节点。但是这种直接从数据库中取得任务id的方式实在有违天道人伦。而在开发中,我们一般都会查询当前用户的任务列表,然后获取到任务id后再去完成当前任务。代码如下:
@Test
public void completeTask(){
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
TaskService taskService = processEngine.getTaskService();
List<Task> taskList = taskService.createTaskQuery()
.taskCandidateOrAssigned("xialuo")//任务办理人
.list();//返回任务列表
for (Task task : taskList) {
taskService.complete(task.getId());
}
}
至此完成当前任务,业务流程走向【教师】初步审批节点,大家可以自行参上上述方式查看数据库表的变化并自行完成后续任务的完成。入门案例我们讲解到这里。