上一讲我们画了一个简单的请假的业务流程图,这一讲我们把它部署到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中较实用的功能,这些功能将来会在生产环境中使用。

上一篇:

下一篇:

activiti7(四):activiti开发之实用的功能

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐