工作流最早起源于生产组织和办公自动化领域,它是针对平时工作中的业务流程活动而提出的一个概念。工作流引擎是业务流程管理系统的一部分,它为业务流程的管理系统提供了根据角色、分工和条件等不同决定信息的流转处理规则和路径。 BPMN与ActivitiBPMN是用于构建业务流程图的一种建模语言标准,它从许多已经存在的建模标记中吸收再生的,形成的一套标准的标记语言。BPMN的目的是为用户提供一套容易理解的标准符号,将业务流程建模简单化、图形化,将复杂的建模过程视觉化,让业务建模者对BPMN描述的业务流程都有一个更加清晰明了的了解。Activiti 核心是 BPMN 2.0 的流程引擎,网上关于Activiti 的发展历程文章很多,本文就不再赘述。以典型的请假流程为例:

java如何部署activiti工作流引擎 activiti 工作流引擎_activiti 工作流

对应的BPMN流程定义文件内容如下:

java如何部署activiti工作流引擎 activiti 工作流引擎_activiti 工作流_02

集成Spring

.....

与业务系统整合,集成数据源,事务管理等。


审批单据表activiti一共有25张表,已经提供了足够的审批数据,但业务使用起来可能不大方便,这时候可以增加业务单据表,用于存储业务含义的单据信息,如:标题、业务类型、创建人、提交人、当前状态等。

流程定义发布流程定义发布很简单

 ,可以把流程定义文件放到resources目录下,在Spring容器启动时,通过实现InitializingBean的afterPropertiesSet() 方法实现启动发布。


@Override    public void afterPropertiesSet() throws Exception {        RepositoryService repositoryService = processEngine.getRepositoryService();        repositoryService.createDeployment().addClasspathResource("leave.bpmn20.xml")                .enableDuplicateFiltering().name("请假").category("办公").deploy();    }

其中,enableDuplicateFiltering()启动重复过滤,如果name相同的会认为已经发布过,再次发布时会直接忽略。我们可以使用业务名称+文件MD5作为唯一name。流程定义的发布执行处理如下:

java如何部署activiti工作流引擎 activiti 工作流引擎_activiti 工作流_03

从图可见,流程定义的发布主要是插入ACT_RE_PROCDEF、ACT_RE_DEPLOYMENT、ACT_GE_BYTEARRAY三张表,通过ACT_UNIQ_PROCDEF(KEY_, VERSION_, TENANT_ID_) 唯一索引控制并发唯一性。

启动流程实例

如果说流程定义是“类”,那么流程实例对应是“对象”,一个个流程的节点就是一个个的“方法”,流程变量像Java的变量,可以是类级变量(全局流程变量)或者方法本地变量(本地流程变量)。启动流程实例就是创建对象new Object,完成任务运行过程中可以传入变量,直到驱动整个流程实例的完成。流程变量的使用如下:

= 3}]]>

启动流程实例时传入流程变量day:

Map variables = new HashMap<>();variables.put("day", 4);ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("leave_process", variables);

启动流程实例的SQL执行过程如下:

java如何部署activiti工作流引擎 activiti 工作流引擎_activiti 工作流_04

在这个案例中,启动一个流程实例有8个表的数据insert操作!


完成任务节点


代码实例

TaskService taskService = processEngine.getTaskService();TaskQuery query = taskService.createTaskQuery();Task task = query.processInstanceId(processId).singleResult();if (task != null) {    taskService.complete(task.getId());}

同样,通过生成日志可以看到,一共有5 insert, 3 update, 2 delete. 删除内容为IdentityLinkEntity与Task,即与当前任务节点相关的。

集群环境

activiti是支持集群环境的,包括任务的执行、定时任务等。但某些场景还是需要注意下,比如: id生成器

id生成器接口IdGenerator,所有activiti相关的表ID生成都使用该接口生成ID。默认的实现为DbIdGenerator,它会使用act_ge_property表中的next.dbid存储当前的ID值,每个集群节点通过乐观锁获取新的ID值,步长idBlockSize(默认为2500),SQL如下:

update ACT_GE_PROPERTY SET REV_ = ?, VALUE_ = ? where NAME_ = ? and REV_ = ?

并发情况下如果ID已经被其他节点获取REV_字段的值已被修改,则语句更新结果值为0,而activiti并没有实现乐观锁失败时的重试,快速直接失败:

org.activiti.engine.ActivitiOptimisticLockingException: PropertyEntity[name=next.dbid, value=107501] was updated by another transaction concurrently  at org.activiti.engine.impl.db.DbSqlSession.flushUpdates(DbSqlSession.java:879)  at org.activiti.engine.impl.db.DbSqlSession.flush(DbSqlSession.java:618)  at org.activiti.engine.impl.interceptor.CommandContext.flushSessions(CommandContext.java:212)  at org.activiti.engine.impl.interceptor.CommandContext.close(CommandContext.java:138)  at org.activiti.engine.impl.interceptor.CommandContextInterceptor.execute(CommandContextInterceptor.java:66)  at org.activiti.spring.SpringTransactionInterceptor$1.doInTransaction(SpringTransactionInterceptor.java:47)  at org.springframework.transaction.support.TransactionTemplate.execute(TransactionTemplate.java:133)  at org.activiti.spring.SpringTransactionInterceptor.execute(SpringTransactionInterceptor.java:45)  at org.activiti.engine.impl.interceptor.LogInterceptor.execute(LogInterceptor.java:37)  at org.activiti.engine.impl.cfg.CommandExecutorImpl.execute(CommandExecutorImpl.java:40)  at org.activiti.engine.impl.db.DbIdGenerator.getNewBlock(DbIdGenerator.java:43)  at org.activiti.engine.impl.db.DbIdGenerator.getNextId(DbIdGenerator.java:36)

解决方案可以使用activiti自带的StrongUuidGenerator,或者自己实现全局的ID生成器代替。


并发与事务


activiti是支持集群环境中运行的,所有运行过程状态数据库中存储,如果出现多个节点同时在完成某个节点,则只有一个节点能成功,原理为通过REV_版本字段实现乐观并发控制,我们可以在activiti的很多表中都能看到该字段的背影。以完成任务节点为例,其中一条执行语句为:

==>  Preparing: update ACT_RU_EXECUTION set REV_ = ?, BUSINESS_KEY_ = ?, PROC_DEF_ID_ = ?, ACT_ID_ = ?, IS_ACTIVE_ = ?, IS_CONCURRENT_ = ?, IS_SCOPE_ = ?, IS_EVENT_SCOPE_ = ?, PARENT_ID_ = ?, SUPER_EXEC_ = ?, SUSPENSION_STATE_ = ?, CACHED_ENT_STATE_ = ?, NAME_ = ? where ID_ = ? and REV_ = ? [DEBUG] 2019-10-19 21:26:06,711 method:org.apache.ibatis.logging.jdbc.BaseJdbcLogger.debug(BaseJdbcLogger.java:145)==> Parameters: 2(Integer), null, leave_process:23:77503(String), gen_manager(String), true(Boolean), false(Boolean), true(Boolean), false(Boolean), null, null, 1(Integer), 2(Integer), null, 80001(String), 1(Integer)

同时保证多个语句在同一个事务中执行,当然,也可以集成Spring事务管理器,把业务自身的语句也纳入到事务中。


审批状态转换如果业务需要定义自己的审批状态,其中一个比较好的方式是通过实现ActivitiEventListener事件监听器接口,通过完成与新建的任务节点进行转换,事件监控器只能通过ThreadLocal把事件信息带出去:


public class MyActivitiEventListener implements ActivitiEventListener {    @Override    public void onEvent(ActivitiEvent event) {        switch (event.getType()) {            case TASK_CREATED:                TaskEntity createdTask = ((TaskEntity)((ActivitiEntityEventImpl)event).getEntity());                Leave.approvalNodeThreadLocal.get().createdTask = createdTask;                System.out.println("task create : " + createdTask.getId() + ", " + createdTask.getName());                break;            case TASK_COMPLETED:                TaskEntity completedTask = ((TaskEntity)((ActivitiEntityEventImpl)event).getEntity());                Leave.approvalNodeThreadLocal.get().completedTask = completedTask;                System.out.println("task complete : " + completedTask.getId() + ", " + completedTask.getName());                break;            default:                System.out.println("Event received: " + event.getType());        }    }    // 返回true可以把onEvent的异常往上抛,执行事务可以回滚    @Override    public boolean isFailOnException() {        return true;    }}


小结activiti入门相对简单,能快速上手。工作流引擎本质是让我们通过图形化或DSL(BPMN)快速描述快速实现业务的需求,而不需要写大量的if else来实现状态转换与记录。除了OA单据审批,可以使用activiti的场景也可以有很多,只要涉及流程相关,比如流程编排,规则引擎等。


----------  END  ----------