【Activiti工作流】(三)Activiti之HelloWorld

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&amp;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());
        }
    }

至此完成当前任务,业务流程走向【教师】初步审批节点,大家可以自行参上上述方式查看数据库表的变化并自行完成后续任务的完成。入门案例我们讲解到这里。

做一个最简单的HellWorld流程用代码的方式实现并且走完流程。 1.首先是需要部署流程定义。 2.启动流程实例。 3.查看流程任务以及完成流程任务。 创建一个单元测试类ActivitiHelloWorldTest,首先第一操作流程就必须要获取引擎实例: [java] view plain copy 1. <span 2. * 获取默认的流程引擎实例 会自动读取activiti.cfg.xml文件 3. */ 4. private ProcessEngine processEngine=ProcessEngines.getDefaultProcessEngine();</span> 把绘制的流程定义图(我在上一篇博文上绘制过),部署下: [java] view plain copy 1. <span 2. * 部署流程定义 3. */ 4. @Test 5. public void deploy(){ 6. // 获取部署对象 7. Deployment deployment=processEngine.getRepositoryService() // 部署Service 8. .createDeployment() // 创建部署 9. .addClasspathResource("diagrams/helloWorld.bpmn") // 加载资源文件 10. .addClasspathResource("diagrams/helloWorld.png") // 加载资源文件 11. .name("HelloWorld流程") // 流程名称 12. .deploy(); // 部署 13. System.out.println("流程部署ID:"+deployment.getId()); 14. System.out.println("流程部署Name:"+deployment.getName()); 15. }</span> 接着需要启动流程实例,这样一个流程才开始: [java] view plain copy 1. <span 2. * 启动流程实例 3. */ 4. @Test 5. public void start(){ 6. // 启动并获取流程实例 7. ProcessInstance processInstance=processEngine.getRuntimeService() // 运行时流程实例Service 8. .startProcessInstanceByKey("myFirstProcess"); // 流程定义表的KEY字段值 9. System.out.println("流程实例ID:"+processInstance.getId()); 10. System.out.println("流程定义ID:"+processInstance.getProcessDefinitionId()); 11. }</span> 查看一下李四这个用户的任务信息: [java] view plain copy 1. <span 2. * 查看任务 3. */ 4. @Test 5. public void findTask(){ 6. // 查询并且返回任务即可 7. List<Task> taskList=processEngine.getTaskService() // 任务相关Service 8. .createTaskQuery() // 创建任务查询 9. .taskAssignee("李四") // 指定某个人 10. .list(); 11. for(Task task:taskList){ 12. System.out.println("任务ID:"+task.getId()); 13. System.out.println("任务名称:"+task.getName()); 14. System.out.println("任务创建时间:"+task.getCreateTime()); 15. System.out.println("任务委派人:"+task.getAssignee()); 16. System.out.println("流程实例ID:"+task.getProcessInstanceId()); 17. } 18. }</span> 最后完成HelloWorld节点任务,把流程走完: [java] view plain copy 1. <span 2. * 完成任务 3. */ 4. @Test 5. public void completeTask(){ 6. <span </span>processEngine.getTaskService() // 任务相关Service 7. .complete("2504"); // 指定要完成的任务ID 8. }</span> 有个很重要的概念,流程定义和流程实例的关系,可以把这两种关系理解成是类和对象的关系。 流程定义是一个模板,而流程实例就是通过模板演变出来的具体的可用的东西。 首先当运行:deploy()部署流程定义方法,在数据库中流程定义表会发生一些变化新增了一条数据, act_re_deployment流程定义部署表: 然后act_re_prodef流程定义表也会有一条数据插入: 还有一个act_ge_bytearray表用来存储资源信息: 接着来运行start()启动流程实例: act_ru_task运行时流程任务表新增了一条数据: act_ru_execution运行时流程执行表: act_ru_identitulink是用于执行主体相关信息表: 可以查看刚刚"李四”这个用户的任务: 运行findTask()查看用户任务,控制台输出如下: [java] view plain copy 1. <span Failed to load class "org.slf4j.impl.StaticLoggerBinder". 2. SLF4J: Defaulting to no-operation (NOP) logger implementation 3. SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details. 4. 任务ID:5004 5. 任务名称:HelloWorld 6. 任务创建时间:Mon Mar 13 11:15:18 CST 2017 7. 任务委派人:李四 8. 流程实例ID:5001</span> 查询到了数据就说明这个用户有任务可以执行,接着运行completeTask()方法完成任务: 然后数据库中ru开头的运行时候所有表的数据都没了,因为现在流程结束,不需要这些数据了。 在hi开头的表里,会新增不少数据,这些数据主要是用来归档查询用的,也就是历史数据。 act_hi_taskinst 历史流程实例任务表加了一条任务数据; act_hi_procinst 历史流程实例实例表加了一条流程实例相关信息的数据(包括开始时间,结束时间等等信息); act_hi_identitylink 历史流程实例参数者的表加了一条数据; act_hi_actinst 历史活动节点表加了条流程活动节点信息的数据(每个流程实例具体的执行活动节点的信息);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值