Activiti

http://www.activiti.org/

Activiti是一个工作流引擎,可以将业务系统中复杂的业务流程抽取出来,使用专门的建模语言BPMN2.0进行定义,业务流程按照预先定义的流程进行执行,实现了系统的流程由activiti进行管理,减少业务系统由于流程变更进行系统升级改造的工作量,从而提高系统的健壮性,同时也减少了系统维护开发的成本。

Activiti的基本概念与使用

Activiti负责流程的自动化控制,他通过25张数据库表存储所有流程的需要的数据,修改这些数据,来实现流程的自动化运行。为了方便实现流程操作,Activiti提供了多个Service类来实现业务控制,从而对数据库进行操作。

Activiti的使用大致分为四个步骤:

  1. 使用BPMN绘制出流程定义

  2. 部署流程定义

  3. 使用流程定义创建流程实例

  4. 操作流程实例中的各个task

定义入下申请单的BPMN20.xml流程:

<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:activiti="http://activiti.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://www.activiti.org/test">
  <process id="leave" name="请假单" isExecutable="true">
    <startEvent id="startevent1" name="Start"/>
    <userTask id="usertask1" name="创建请假单" activiti:assignee="zhangsan"/>
    <sequenceFlow id="flow1" sourceRef="startevent1" targetRef="usertask1"/>
    <userTask id="usertask2" name="部门经理审核" activiti:assignee="lisi"/>
    <sequenceFlow id="flow2" sourceRef="usertask1" targetRef="usertask2"/>
    <userTask id="usertask3" name="人事复核" activiti:assignee="wangwu"/>
    <sequenceFlow id="flow3" sourceRef="usertask2" targetRef="usertask3"/>
    <endEvent id="endevent1" name="End"/>
    <sequenceFlow id="flow4" sourceRef="usertask3" targetRef="endevent1"/>
  </process>
  <bpmndi:BPMNDiagram id="BPMNDiagram_myProcess">
    <bpmndi:BPMNPlane bpmnElement="myProcess" id="BPMNPlane_myProcess">
      <bpmndi:BPMNShape bpmnElement="startevent1" id="BPMNShape_startevent1">
        <omgdc:Bounds height="35.0" width="35.0" x="130.0" y="160.0"/>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="usertask1" id="BPMNShape_usertask1">
        <omgdc:Bounds height="55.0" width="105.0" x="210.0" y="150.0"/>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="usertask2" id="BPMNShape_usertask2">
        <omgdc:Bounds height="55.0" width="105.0" x="360.0" y="150.0"/>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="usertask3" id="BPMNShape_usertask3">
        <omgdc:Bounds height="55.0" width="105.0" x="510.0" y="150.0"/>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNShape bpmnElement="endevent1" id="BPMNShape_endevent1">
        <omgdc:Bounds height="35.0" width="35.0" x="660.0" y="160.0"/>
      </bpmndi:BPMNShape>
      <bpmndi:BPMNEdge bpmnElement="flow1" id="BPMNEdge_flow1">
        <omgdi:waypoint x="165.0" y="177.0"/>
        <omgdi:waypoint x="210.0" y="177.0"/>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="flow2" id="BPMNEdge_flow2">
        <omgdi:waypoint x="315.0" y="177.0"/>
        <omgdi:waypoint x="360.0" y="177.0"/>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="flow3" id="BPMNEdge_flow3">
        <omgdi:waypoint x="465.0" y="177.0"/>
        <omgdi:waypoint x="510.0" y="177.0"/>
      </bpmndi:BPMNEdge>
      <bpmndi:BPMNEdge bpmnElement="flow4" id="BPMNEdge_flow4">
        <omgdi:waypoint x="615.0" y="177.0"/>
        <omgdi:waypoint x="660.0" y="177.0"/>
      </bpmndi:BPMNEdge>
    </bpmndi:BPMNPlane>
  </bpmndi:BPMNDiagram>
</definitions>

部署Activiti,部署流程定义,并使用流程定义开启一个流程实例:

部署Activiti

Activiti支持的数据库

Activiti在运行时需要数据库支持,目前共支持:

  • H2 默认

  • Mysql

  • Oracle

  • DB2

  • Postgres

  • MsSql

使用MySQL生成Activiti库

创建Activiti的数据库:

创建用于生成表的Maven工程,并添加如下的依赖:

创建activiti.cfg.xml,指定数据库配置:

使用代码初始化数据库:

创建流程引擎

通过流程引擎的配置类(ProcessEngineConfiguration)可以创建工作流引擎对象ProcessEngine。常用的配置类有:

  • StandaloneProcessEngineConfiguration:这个配置会使用默认的配置文件activiti.cfg.xml,并寻找processEngineConfiguration实例,然后创建流程引擎对象,他会创建一个独立的Spring环境,然后执行。

  • SpringProcessEngineConfiguration:它不会创建IoC容器,而是直接与Spring进行整合。

示例:

businessKey

在发起流程时,还可以指定businessKey字段:

该字段代表在该流程下,这个业务的唯一id值,如果这个流程是请假单,那么businessKey就是请假单业务表中的主键,在实际的应用场景中,通常都会制定businessKey字段。

在流程实例的运行阶段,businessKey被记录在表ACT_RU_EXECUTION中,该表就是流程实例表。如果任务的执行人要找到对应的业务数据(businessKey),他可以从ACT_RU_TASK表中根据PROC_INST_ID字段,该字段就是流程实例的id字段,找到对应的流程实例记录,从而找到businessKey。

在历史记录中,类似运行阶段, 可以从ACT_HI_PROCINST表中,找到对应的流程实例,从而拿到业务id。

其他表类似,都是通过关联字段找到流程实例记录,并找到businessKey。

挂起、激活流程

通常到达月底时,财务需要封账,所有的报销流程,可能都需要挂起,等到月初再激活再次处理。针对这种情况,Activiti提供了相应的功能已支持。

挂起/激活指定的流程实例

被挂起的流程实例,不可以进行任何任务处理,否则会抛出异常ActivitException

挂起/激活指定的流程

流程也可以被挂起,被挂起的流程不能启动新的流程,如果启动会报错ActivitiException,而在挂起之前启动的流程实例则不会受到影响。

任务处理-为task指定Assignee

在BPMN流程图中,含有多种任务,任务可以指定任务责任人(assignee),责任人的分配又包含多种方式。

固定分配

所谓固定分配,就是将责任人在绘制时直接写入到bpmn文件中,比如如下的BPMN文件的部分显示的含义为,创建请假单的任务的责任人为zhangsan。

但是大多数的情况下,任务的责任人通常不应该在流程图绘制的过程中就指定他们,通常是作为变量传入到流程中,再启动流程实例。

表达式分配

大多数情况下,我们都会使用UEL表达式为任务分配责任人。UEL是Unified Expression Language,统一表达式语言。Activiti支持两种UEL表达式:UEL-value 和 UEL-method。

简单的来说,就是在定义BPMN的时候,为Task分配的assignee不再是静态值,而是一个UEL表达式,然后程序在启动实例的时候,为流程指定所有的UEL表达式的值,即可完成责任人的自定义分配。

为BPMN指定UEL表达式:

部署流程,启动并指定负责人的值:

上面的方式是通过UEL-value获取的,我们也可以通过UEL-method来设置:

定义BPMN的值为${userBean.getUserName()},然后再发起流程实例的时候,指定userBean,会自动调用getUserName()方法。

此外,还可以写入一些特殊的表达式,比如果请假天数大于5天,换王富贵审批:

任务监听器分配

除了表达式分配,还有一种监听器分配的方式,他会为UserTask分配一个任务监听器(Task Listeners),当与用户任务相关的生命周期事件发生时触发。例如,在任务被分配给用户之前或之后,或者在用户完成任务时,可以执行一些定制的操作,如更新数据库记录、发送通知邮件、审计日志记录等。

实现任务监听需要先实现接口TaskListener,任务监听器监听的时间类型分为:

  • create 任务创建后事件(分配之前)

  • assignment 任务分配后事件

  • complete 任务完成后事件

  • delete 任务删除后事件

首先,创建TaskListener实现:

其次,在BPMN中,为所有的任务指定事件类型为create的任务监听器,这样任务创建完毕后,都会为他们设置一个责任人。

图 2

任务查询

查询指定负责人的、指定流程的、指定业务id的待办任务:

任务办理

只有当前责任人才能完成任务,在调用完成任务的方法前,需要我们先校验任务的责任人是否是当前人,并调用complete方法进行任务办理:

流程变量

流程运转时,有时会需要流程变量,业务系统和Activiti结合时少不了流程变量,流程变量就是Activiti在管理工作流时根据管理需要而设置的变量。比如在出差流程中,如果出差天数大于三天,则由总经理审核,否则则由人事直接审核。这里,出差天数就是一个流程变量,需要在流程流转时使用。

  1. 在属性上使用UEL表达式,比如在Assignee出设置读取变量${assigee}assignee就是一个类流程变量名称。

  2. 在连线上使用UEL表达式,比如在连线处设置:${price < 1000}。price就是一个流程变量名称,uel表达式结果类型为布尔类型,根据值返回流程的走向。

他们的变量名称都是不可以重复的,如果重复设置,后面的会覆盖前面的。

流程变量的类型

Type Name
Description

string

java.lang.String

integer

java.lang.Integer

short

java.lang.Short

long

java.lang.Long

double

java.lang.Double

boolean

java.lang.Boolean

date

java.util.Date

binary

bytes array

serializable

必须实现Serializable接口,并且拥有UID

设置流程变量

在连接线处设置分支判断:

设置流程变量共有三种方式:

  1. 发起流程时,设置流程变量

  2. 在变量被使用的前一步的complete方法设置:

  3. 在变量被使用的前一步,使用setVariables方法设置:

获取流程变量

局部流程变量

局部流程变量不同于流程变量,是用于在某个任务阶段,记录任务的额外信息的。

设置局部流程变量

局部流程变量只能在该任务节点中设置和获取,在其他任务节点中,无法获取。

注意,连线是属于头任务的,也就是说如果头任务。

获取局部流程变量

组任务

在流程定义中任务节点的assignee固定设置任务负责人,将参与者固定设置在bpmn文件中,如果临时任务负责人变更则需要修改流程定义,系统可扩展性则比较差。针对这种情况,可以设置多个候选人,从多个候选人中选择参与者来完成任务。

我们可以通过在BPMN中对对应的任务设置候选人和候选组来完成此类需求的设置:

  1. Candidate Users

  2. Candidate Group

设置候选人

在BPMN中设置 Candidate Users,多个候选人之间使用英文逗号隔开:

当任务流程进入到人事复合节点时,zhangsan、lisi、wangwu、zhaoliu都可以进行任务的完成操作,需要候选人中的某个人拾取任务:

查询作为候选人的的任务待办:

如果拾取任务之后,不想继续处理任务,可以归还任务:

任务负责人也可以将任务交接给其他人:

网关

网关用来控制流程的流向。

排他网关 ExclusiveGateway

排他网关

排他网关,用来在流程中实现决策。 当流程执行到这个网关,所有分支都会判断条件是否为true,如果为true则执行该分支,

注意:排他网关只会选择一个为true的分支执行。如果有两个分支条件都为true,排他网关会选择id值较小的一条分支去执行。

为什么要用排他网关?

不用排他网关也可以实现分支,如:在连线的condition条件上设置分支条件。 在连线设置condition条件的缺点:如果条件都不满足,流程就结束了(是异常结束)。

并行网关 ParallelGateway

并行网关

并行网关允许将流程分成多条分支,也可以把多条分支汇聚到一起,并行网关的功能是基于进入和外出顺序流的:

  • fork分支:并行后的所有外出顺序流,为每个顺序流都创建一个并发分支。

  • join汇聚:所有到达并行网关,在此等待的进入分支, 直到所有进入顺序流的分支都到达以后, 流程就会通过汇聚网关。

注意:

  • 如果同一个并行网关有多个进入和多个外出顺序流, 它就同时具有分支和汇聚功能。 这时,网关会先汇聚所有进入的顺序流,然后再切分成多个并行分支。

  • 并行网关不会解析条件,即使顺序流中定义了条件,也会被忽略。

包含网关 InclusiveGateway

包含网关

包含网关可以看做是排他网关和并行网关的结合体。

和排他网关一样,你可以在外出顺序流上定义条件,包含网关会解析它们。 但是主要的区别是包含网关可以选择多于一条顺序流,这和并行网关一样。

包含网关的功能是基于进入和外出顺序流的:

  • 分支: 所有外出顺序流的条件都会被解析,结果为true的顺序流会以并行方式继续执行, 会为每个顺序流创建一个分支。

  • 汇聚:所有并行分支到达包含网关,会进入等待状态, 直到每个包含流程token的进入顺序流的分支都到达。 这是与并行网关的最大不同。换句话说,包含网关只会等待被选中执行了的进入顺序流。 在汇聚之后,流程会穿过包含网关继续执行。

事件网关 EventGateway

事件网关

事件网关允许根据事件判断流向。网关的每个外出顺序流都要连接到一个中间捕获事件。 当流程到达一个基于事件网关,网关会进入等待状态:会暂停执行。与此同时,会为每个外出顺序流创建相对的事件订阅。

事件网关的外出顺序流和普通顺序流不同,这些顺序流不会真的"执行", 相反它们让流程引擎去决定执行到事件网关的流程需要订阅哪些事件。 要考虑以下条件:

  1. 事件网关必须有两条或以上外出顺序流;

  2. 事件网关后,只能使用intermediateCatchEvent类型(activiti不支持基于事件网关后连接ReceiveTask)

  3. 连接到事件网关的中间捕获事件必须只有一个入口顺序流。

Service 接口

创建流程引擎后,通过引擎对象,获取针对不同的业务的操作Service,目前共有如下几个Service:

Activiti在创建ProcessEngine后,会派生出来多个Service为业务程序提供服务,这些API最终都会操作对应数据库的表,并且可以通过getXxxService()方法获取他们,目前主要拥有的Service有:

  • RepositoryService,资源管理

  • TaskService,任务管理

  • RuntimeService,运行管理

  • ManagementService,引擎管理

  • HistoryService,历史管理

  • IdentityService,已删除

  • FormService,已删除

Activiti的表结构

共分为四类:

  • ACT_RE_*,RE代表Repository,仓库;是用于存储流程定义、流程部署、流程模型等相关数据的。

  • ACT_RU_*,RU代表Runtime,运行时;流程启动到结束过程中所有运行的数据全放在该表中。结束后会从这些表中删除掉。

  • ACT_HI_*,HI代表History,历史;是所有流程实例运行的历史信息,其中的表名和字段名,与运行时表相对应。

  • ACT_GE_*,GE代表General,通用;是用于记录一些其他的通用的数据,比如bpmn文件流、系统属性。

ACT_RE_PROCDEF 流程定义表

ACT_RE_DEPLOYMENT 流程部署记录表

存储流程的部署记录。相同的key不同的内容多次部署,会产生多条部署记录。

ACT_RE_MODEL 流程模型表

用于存储每个流程最新的一个流程模型,可通过API来操作。

ACT_GE_BYTEARRAY 二进制资源表

表分类
表名
解释

流程定义表

RE:Repository,流程仓库,包含了流程定义与流程静态资源

ACT_RE_DEPLOYMENT

ACT_RE_MODEL

模型信息

ACT_RE_PROCDEF

已部署的流程定义

一般数据

GE:General,提供给不同场景使用的通用数据

ACT_GE_BYTEARRAY

比特流数据,一般用于存储所上传的文件流

ACT_GE_PROPERTY

系统相关属性

运行定义表

RU:Runtime,运行时信息,流程开始运行会插入数据,运行结束会删除数据

ACT_RU_DEADLETTER_JOB

ACT_RU_EVENT_SUBSCR

ACT_RU_EXECUTION

ACT_RU_IDENTITYLINK

ACT_RU_INTEGRATION

ACT_RU_JOB

ACT_RU_SUSPENDED_JOB

ACT_RU_TASK

ACT_RU_TIMER_JOB

ACT_RU_VARIABLE

流程历史记录

HI:History 历史数据,比如历史流程实例、变量、任务

ACT_HI_ACTINST

ACT_HI_ATTACHMENT

历史的流程附件

ACT_HI_COMMENT

历史的说明性信息

ACT_HI_DETAIL

历史的流程运行中的细节信息

ACT_HI_IDENTITYLINK

历史的流程运行中的用户关系

ACT_HI_PROCINST

历史的流程实例

ACT_HI_TASKINST

历史的任务实例

ACT_HI_VARINST

示例的流程运行中的变量信息

部署流程

部署一个请假申请单流程,根据定义,会执行如下三条SQL:

最后更新于

这有帮助吗?