Activiti调研以及双人复合的Demo演示
1.说明
Activiti是一种业务流程管理(BPM)框架,它是覆盖了业务流程管理、工作流、服务协作等领域的一个开源的、灵活的、易扩展的可执行流程语言框架。Activiti基于Apache许可的开源BPM平台,它特色是提供了Eclipse插件,开发人员可以通过插件直接绘画出业务,结合相应的业务处理代码,能够灵活handle各自复杂业务流程处理需求。
2.软件环境准备
我们在Idea里尝试使用Activiti,但是官网并没有Idea的插件,只有Eclispe平台的。但是在plugin repo里找到了ActiBPM, ActiBPM插件非常难用,也无法生成图片,评论里很多吐槽,而且在2014年因为违反GPL已经停止维护。目前Activiti在Idea平台并不好用,也就是将就用的程度,在你组织好结构以后,修改了审批人是没法保存的,这个我看其他评论里也遇到了,修改了自动化流程的Class路径和表达式后却无法保存。这个很无奈,考虑现场大家使用的工具是IDEA。我尝试编辑好流程以后,用文本编辑器根据语法自己去做微调,这时候就不要再用插件再次打开bpmn文件了,因为你再次保存,插件可能把你辛苦调整的参数给破坏。
- 安装插件
- maven
略 - 拖拽设计流程
因为Plugin的bug,导致修改左侧的参数无法保存。需要手工修改Bpmn文件。如下,重点关注下serviceTask这一项。
<process id="cmpc_test" isClosed="false" isExecutable="true" processType="None">
<startEvent id="_2" name="StartEvent"/>
<userTask activiti:assignee="${userId}" activiti:exclusive="true" id="_3" name="提交双人复合"/>
<userTask activiti:assignee="复合管理员" activiti:exclusive="true" id="_5" name="审批"/>
<endEvent id="_4" name="EndEvent"/>
<sequenceFlow id="_6" sourceRef="_2" targetRef="_3"/>
<sequenceFlow id="_7" sourceRef="_3" targetRef="_5"/>
<serviceTask activiti:delegateExpression="${serviceClassDelegateSample}" activiti:exclusive="true" id="_8" name="执行"/>
<sequenceFlow id="_9" name="同意" sourceRef="_5" targetRef="_8">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${result=='agree'}]]></conditionExpression>
</sequenceFlow>
<sequenceFlow id="_10" sourceRef="_8" targetRef="_4"/>
<sequenceFlow id="_11" name="拒绝" sourceRef="_5" targetRef="_4">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${result=='deny'}]]></conditionExpression>
</sequenceFlow>
</process>
3. Demo展示
根据上边的设计流程,准备了一份业务代码,我贴出来maven配置,以及activiti的配置文件。
- maven 配置
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>test</groupId>
<artifactId>test</artifactId>
<packaging>war</packaging>
<version>1.0-SNAPSHOT</version>
<name>test Maven Webapp</name>
<url>http://maven.apache.org</url>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.6</version>
</dependency>
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-engine</artifactId>
<version>5.22.0</version>
</dependency>
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-spring</artifactId>
<version>5.22.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.8-dmr</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>4.3.14.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>4.3.14.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version> 3.2.4.RELEASE </version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<finalName>test</finalName>
</build>
</project>
这里是常用的依赖。
- activiti 配置
<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="processEngineConfiguration" class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">
<property name="jdbcDriver" value="com.mysql.cj.jdbc.Driver"/>
<property name="jdbcUrl" value="jdbc:mysql://18.217.123.86:3306/activiti?useUnicode=true&characterEncoding=utf8"/>
<property name="jdbcUsername" value="root"/>
<property name="jdbcPassword" value="123456"/>
<property name="databaseSchemaUpdate" value="true"/>
</bean>
<!--加载属性文件-->
<bean id="alaudaServiceSample" class="com.springdemo.controller.AlaudaServiceImpl">
</bean>
<bean id="serviceClassDelegateSample" class="com.springdemo.controller.ServiceClassDelegateSample">
<property name="alaudaService" ref="alaudaServiceSample" />
</bean>
<!--<context:component-scan base-package="com.springdemo.controller" />-->
</beans>
配置自动扫描的情况下,spring无法注入自动任务中的Spring bean 对象,导致空指针。去官网查,目前只支持通过显式声明来做内部对象注入,给的用法也是显式声明,配置并不是和Spring配置文件放在一起,所以目前看,并不影响在中信的统一平台3.0中去使用。
- Code
package com.springdemo.controller;
import org.activiti.engine.*;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.task.Task;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@RunWith(SpringJUnit4ClassRunner.class) //使用junit4进行测试
@ContextConfiguration(locations={"classpath:applicationContext.xml"}) //加载配置文件
public class ActivitiTest2 extends AbstractJUnit4SpringContextTests {
@Test
public void createActivitiEngine(){
/**
* 1. 通过ProcessEngines 来获取默认的流程引擎
*/
// 默认会加载类路径下的 activiti.cfg.xml
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
System.out.println("通过ProcessEngines 来获取流程引擎");
/**
* 2. 部署流程
*/
Deployment deployment = processEngine.getRepositoryService().createDeployment().name("双人复合")
.addClasspathResource("diagrams/recheck.bpmn").deploy();
System.out.println("部署的id "+deployment.getId());
System.out.println("部署的名称 "+deployment.getName());
/**
* 3. 准备数据
*/
Map<String,Object> params=new HashMap<String, Object>();
params.put("userId", "xialingming");
ApplyInfoBean applyInfoBean = new ApplyInfoBean();
applyInfoBean.setId(1);
applyInfoBean.setCost(300);
applyInfoBean.setDate(new Date());
applyInfoBean.setAppayPerson("夏某某");
/**
* 4. 发起一个流程
*/
//流程定义的key
String processDefinitionKey = "cmpc_test";
ProcessInstance pi = processEngine.getRuntimeService()
.startProcessInstanceByKey(processDefinitionKey, params);
System.out.println("流程实例ID:"+pi.getId());//流程实例ID
System.out.println("流程定义ID:"+pi.getProcessDefinitionId());//流程定义ID
//把数据传给任务
TaskService taskService = processEngine.getTaskService();//与正在执行的任务管理相关的Service
Task taskFirst = taskService.createTaskQuery().processInstanceId(pi.getId()).singleResult();
taskService.setVariable(taskFirst.getId(), "applyInfoBean", applyInfoBean);
String taskId = taskFirst.getId();
//taskId:任务id
processEngine.getTaskService().complete(taskId);
System.out.println("当前任务执行完毕");
String assignee2 = "复合管理员";
List<Task> list2 = processEngine.getTaskService()//与正在执行的任务管理相关的Service
.createTaskQuery()//创建任务查询对象
.taskAssignee(assignee2)//指定个人任务查询,指定办理人
.list();
if(list2!=null && list2.size()>0){
for(Task task2:list2){
ApplyInfoBean appayBillBean=(ApplyInfoBean) taskService.getVariable(task2.getId(), "applyInfoBean");
//通过夏某某的流程
if(appayBillBean != null && appayBillBean.getAppayPerson().equals("夏某某")) {
Map<String, Object> variables = new HashMap<String, Object>();
variables.put("result", "agree");
//与正在执行的任务管理相关的Service
processEngine.getTaskService().complete(task2.getId(), variables);
System.out.println("完成任务:任务ID:"+task2.getId());
}
}
}
}
}
自动触发的任务是如何实现的:
package com.springdemo.controller;
import org.activiti.engine.delegate.DelegateExecution;
import org.activiti.engine.delegate.JavaDelegate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import java.io.Serializable;
import java.util.logging.Logger;
@Service
public class ServiceClassDelegateSample implements JavaDelegate,Serializable {
private static AlaudaService alaudaService;
private final Logger log = Logger.getLogger(ServiceClassDelegateSample.class.getName());
public void execute(DelegateExecution execution) throws Exception {
log.info(alaudaService.sayHello("xialingming"));
Thread.sleep(10000);
execution.setVariable("task1", "I am task 1");
log.info("I am task 1.");
}
public void setAlaudaService(AlaudaServiceImpl alaudaService) {
this.alaudaService = alaudaService;
}
}
4. 执行结果演示:
运行流程后:
以上是我在自己电脑上适用了activiti。总体来说比较轻量,和Spring结合也还好,不是很自动化(也可能是还没找到更酷的方法)。走了workflow以后,代码架构会更清晰一些,但是也带来了一些不够灵活的地方,比如之前的值班对接双人复合的修改,可能要重新设计,改造;双人复合过于简单,是否值得去使用一个相对复杂的工作流引擎,也是值得考虑的事。