这是篇关于Activiti工作流的教程,此教程通过编码的方式让大家快速掌握Activiti的使用方法,关于Activiti的理论性知识,大家可在学习这篇文章后,自行百度了解,此处不做赘述。
准备工作:IDEA需要安装actibpm插件,该插件在新版本IDEA中搜不到,需要去官网自行下载,然后导入。下载地址:actibpm下载地址,下载完成后是一个actibpm.jar,在IDEA的plugins中点击install plugin from Disk导入jar包即可。
- 使用Maven创建一个普通web项目,并导入Activiti及MySQL相关依赖(activiti需要数据库支持才能工作)。下面附上pom文件的相关代码。
<!--定义版本号--> <properties> <java.version>1.8</java.version> <slf4j.version>1.6.6</slf4j.version> <log4j.version>1.2.12</log4j.version> <activiti.version>7.0.0.Beta1</activiti.version> </properties> <dependencies> <!--activiti核心包--> <dependency> <groupId>org.activiti</groupId> <artifactId>activiti-engine</artifactId> <version>${activiti.version}</version> </dependency> <!--activiti与spring整合的包--> <dependency> <groupId>org.activiti</groupId> <artifactId>activiti-spring</artifactId> <version>${activiti.version}</version> </dependency> <!--bpmn模型处理--> <dependency> <groupId>org.activiti</groupId> <artifactId>activiti-bpmn-model</artifactId> <version>${activiti.version}</version> </dependency> <!--bpmn转换--> <dependency> <groupId>org.activiti</groupId> <artifactId>activiti-bpmn-converter</artifactId> <version>${activiti.version}</version> </dependency> <!--bpmn json数据转换--> <dependency> <groupId>org.activiti</groupId> <artifactId>activiti-json-converter</artifactId> <version>${activiti.version}</version> </dependency> <!--bpmn布局--> <dependency> <groupId>org.activiti</groupId> <artifactId>activiti-bpmn-layout</artifactId> <version>${activiti.version}</version> </dependency> <!--bpmn云支持--> <dependency> <groupId>org.activiti.cloud</groupId> <artifactId>activiti-cloud-services-api</artifactId> <version>${activiti.version}</version> </dependency> <!--mysql驱动--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.11</version> </dependency> <!--mybatis--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.4.5</version> </dependency> <!--连接池--> <dependency> <groupId>commons-dbcp</groupId> <artifactId>commons-dbcp</artifactId> <version>1.4</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> <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>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.4</version> </dependency> </dependencies>
- 在resources目录下,创建一个名为log4j.properties的日志配置文件,用于监测activiti工作流的日志信息。log4j.properties的详情配置如下:
log4j.rootCategory=debug,CONSOLE,LOGFILE log4j.logger.org.apache.axis.enterprise=FATAL,CONSOLE 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\n log4j.appender.LOGFILE=org.apache.log4j.FileAppender #日志的存放路径 log4j.appender.LOGFILE.File=d:\\SOFT\\activiti.log log4j.appender.LOGFILE.Append=true log4j.appender.LOGFILE.layout=org.apache.log4j.PatternLayout log4j.appender.LOGFILE.layout.ConversionPattern=%d{ISO8601} %-6r[%15.15t] %-5p %30.30c %x - %m\n
- 使用activiti提供的默认方式来创建mysql的表。默认方式的要求是在resources下创建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" xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" 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.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> <!--dbcp连接池--> <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"> <!--配置数据库相关的信息--> <!--数据库驱动--> <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/> <!--数据库连接--> <property name="url" value="jdbc:mysql:///activitidb?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=UTF-8"/> <!--数据库用户名--> <property name="username" value="root"/> <!--数据库密码--> <property name="password" value="root"/> <property name="maxActive" value="3"/> <property name="maxIdle" value="1"/> </bean> <!--在默认方式下,这个bean的id必须是processEngineConfiguration。配置完这些内容,activiti会到指定的数据库自动生成需要的表--> <bean id="processEngineConfiguration" class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration"> <!--引用数据源--> <property name="dataSource" ref="dataSource"/> <!--activiti生成数据表时的策略 设置为true时:如果数据库存在相应的表,则直接使用,否则创建--> <property name="databaseSchemaUpdate" value="true"/> </bean>
- 在test目录下新建一个类,名字随便起。然后写入以下代码,用于生成activiti所需的25张表。
public class ActivitiUtil { /** *使用activiti提供的默认方式来创建mysql的表 */ @Test public void createTable(){ //执行该方法,会在数据库自动生成activiti所需的25张表 ProcessEngine engine = ProcessEngines.getDefaultProcessEngine(); System.out.println(engine); } }
- 代码执行后,去数据库可以发现已经生成了25张数据表,这些数据表都是以act为前缀的,表明是activiti所需的数据表。其中act_ge_*用于存放通用类型的数据;act_hi_*存放历史数据;act_re_*存放流程定义的内容和所需静态资源;act_ru_*存放activiti运行时所需的数据。
表名 解释 act_ge_bytearray 通用的流程定义和流程资源 act_ge_property 系统相关属性 act_hi_actinst 历史的活动信息 act_hi_attachment 历史的流程附件 act_hi_comment 历史的说明性信息 act_hi_detail 历史的流程运行中的细节信息 act_hi_identitylink 历史的流程运行过程中用户关系 act_hi_procinst 历史的流程实例 act_hi_taskinst 历史的任务实例 act_hi_varinst 历史的流程运行中的变量信息 act_re_deployment 部署单元信息 act_re_model 模型信息 act_re_procdef 已部署的流程定义 act_ru_event_subscr 运行时事件 act_ru_execution 运行时流程执行实例 act_ru_identitylink 运行时用户关系信息,存储任务节点与参与者的相关信息 act_ru_job 运行时作业 act_ru_task 运行时任务 act_ru_variable 运行时变量表 - 观察下面的类关系图,通过activiti.cfg.xml的配置,activiti会生成5个service,用于操作25张表。ReposituryService用来操作act_re_*表,HistoryService用来操作act_hi_*表,RuntimeService用来操作act_ru_*表。
-
接下来介绍如何在IDEA中创建流程定义。不过在操作之前,大家需要设置IDEA的编码格式,不然在使用bpmn时会出现中文乱码。点击菜单栏Help->Edit Custom VM options,打开文件,在文件的最后一行加入编码格式,如下所示
-Dfile.encoding=UTF-8
准备完毕后,在resources目录下创建bpmn目录,然后在bpmn目录下新建一个bpmn文件,命名随意,但最好有意义。比如我要做请假流程,此处命名为Leave.bpmn。bpmn的内容如下图所示。此处我们设置了3个节点:提交流程申请、部门经理审批和财务审批,对应的执行人分别设置为worker、manager和money。
bpmn文件的内容本质为XML文件,我们可以把新建的Leave.bpmn复制并重命名为Leave.bpmn.xml,查看相关的xml内容。其次我们还可以将绘制的流程导出图片,可供业务人员理解。具体如下:右键Leave.bpmn.xml文件,选择Diagrams–Show BPMN 2.0 Designer,弹出的文件就是图片化的流程,然后点击Export to Image File按钮,选择存储路径,就可以将图片保存下来。
- bpmn定义好之后,我们需要把定义好的流程保存到数据库中,通过activiti提供的API即可。在你的test目录的java文件下写入以下代码:
/** * 部署流程定义 文件上传方式 * 就是把定义好的bpmn,保存到数据库中 */ @Test public void testDeployment() { //创建processEngine ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); //得到RepositoryService实例 RepositoryService repositoryService = processEngine.getRepositoryService(); //使用repositoryService进行部署 Deployment deployment = repositoryService.createDeployment() .addClasspathResource("bpmn/Leave.bpmn")//添加bpmn资源 .addClasspathResource("bpmn/Leave.bpmn.png")//添加png资源 .name("请假申请流程") .deploy(); //输出部署信息 System.out.println("流程部署Id:" + deployment.getId()); System.out.println("流程部署名称:" + deployment.getName()); }
- 流程定义好了,数据库也存好了,接下来就要启动定义好的流程了。通过下列代码即可开启一个流程实例:
/* *启动流程实例,相当于开启一个流程 */ @Test public void testStartProcess() { //创建processEngine ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); //获取RunTimeService RuntimeService runtimeService = processEngine.getRuntimeService(); //启动流程,这个 key是当时定义Leave.bpmn时填写的id(即第一张bpmn图片,左侧蓝色框框中,id对应的值,这个是自定义的) ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("myLeave"); //输出内容 System.out.println("流程定义id:" + processInstance.getProcessDefinitionId()); System.out.println("流程实例id:" + processInstance.getId()); System.out.println("当前活动id:" + processInstance.getActivityId()); }
- 流程启动成功后,就要查询当前个人待执行的任务,此次任务的执行人分别是worker->manager->money。
/** * 查询当前个人待执行的任务 */ @Test public void testFindPersonalTaskList() { //任务负责人 String assignee = "worker"; ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); //创建TaskService TaskService taskService = processEngine.getTaskService(); //根据流程key和任务负责人查询任务。查询流程实例,有没有需要worker执行的节点 List<Task> list = taskService.createTaskQuery() //.processDefinitionKey("myLeave") //可以通过流程的key来查询 .processInstanceId("25001") //可以通过某个流程实例的id查询 .taskAssignee(assignee) .list(); for (Task task : list) { System.out.println("流程实例id:" + task.getProcessInstanceId()); System.out.println("任务id:" + task.getId()); System.out.println("任务负责人:" + task.getAssignee()); System.out.println("任务名称:" + task.getName()); } }
- 上面代码执行完毕后,如果在控制台打印了内容(注:流程实例id是指这个流程的id,任务id是指当前节点的id),说明这个流程实例,有需要worker处理的节点,所以我们需要worker来完成当前节点,推动流程前进,所以,使用下面的代码,完成当前节点(需要worker执行的工作)。注意:下面代码执行完毕后,worker的工作就完成了,下一个节点需要由manager来完成。
//完成任务。当worker的任务执行完毕时,该流程向前推进,到了manager需要完成的节点 @Test public void completeTask() { //获取引擎 ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); //获取taskService TaskService taskService = processEngine.getTaskService(); //根据流程key和任务负责人 查询任务 //返回一个任务对象,查询实例id是25001,执行者是worker的任务 Task task = taskService.createTaskQuery() // .processDefinitionKey("myLeave") .processInstanceId("25001") .taskAssignee("worker") .singleResult(); //完成任务,参数:任务Id taskService.complete(task.getId()); }
- 到目前为止,我们已经学会了通过bpmn定义流程,然后开启一个流程实例,并由相关人员(worker、manager等)推动流程前进。其实在bpmn文件中,我们可以定义多个流程,(虽然可以定义多个流程,但是为了容易理解,最好别这么做。建议一个bpmn定义一个流程,便于理解),接下来,我们通过代码,来查看某个bpmn文件下,定义的所有流程。
//查询流程定义,一个bpmn可以定义多个流程,该方法就是查询一个bpmn下定义的所有流程 @Test public void queryProcessDefinition() { //获取引擎 ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); //获取repositoryService RepositoryService repositoryService = processEngine.getRepositoryService(); //得到ProcessDefinitionQuery对象 ProcessDefinitionQuery processDefinitionQuery = repositoryService.createProcessDefinitionQuery(); //查询出当前所有的流程定义,查询Leave.bpmn文件定义了哪些流程(注意:Leave.bpmn在创建时,我们给他的id是myLeave) List<ProcessDefinition> definitionList = processDefinitionQuery.processDefinitionKey("myLeave") .orderByProcessDefinitionVersion() .desc().list(); //输出流程定义信息 for (ProcessDefinition processDefinition : definitionList) { System.out.println("流程定义id=" + processDefinition.getId()); System.out.println("流程定义name=" + processDefinition.getName()); System.out.println("流程定义key=" + processDefinition.getKey()); System.out.println("流程定义version=" + processDefinition.getVersion()); System.out.println("流程部署id=" + processDefinition.getDeploymentId()); } }
- 定义完流程后,我们可以开启一个流程实例,也可以同时开启多个流程实例。那么,我们怎么查看一个流程下有多少个正在执行的流程实例呢?可以通过下面的代码来实现:
//查询某个流程下的所有进行中的流程实例 @Test public void queryProcessInstance() { //流程定义key String processDefinitionKey = "myLeave"; ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); //获取RuntimeService RuntimeService runtimeService = processEngine.getRuntimeService(); List<ProcessInstance> list = runtimeService.createProcessInstanceQuery() .processDefinitionKey(processDefinitionKey) .list(); for (ProcessInstance processInstance : list) { System.out.println("------------------------"); System.out.println("流程实例id:" + processInstance.getProcessInstanceId()); System.out.println("所属流程定义id:" + processInstance.getProcessDefinitionId()); System.out.println("是否执行完成:" + processInstance.isEnded()); System.out.println("是否暂停:" + processInstance.isSuspended()); System.out.println("当前活动标识:" + processInstance.getActivityId()); System.out.println("业务关键字:" + processInstance.getBusinessKey()); } }
- 如果后期发现某个流程定义得不对,要把它删除掉,该如何操作呢?注意,删除流程时要留意该流程有没有进行中的流程实例,如果存在流程实例,要使用级联删除才行。
//删除流程定义 @Test public void deleteDeployment() { //流程部署id String deploymentId = "2501"; ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); //通过流程引擎获取repositoryService RepositoryService repositoryService = processEngine.getRepositoryService(); //删除流程定义,如果该流程定义已有流程实例启动,则删除时会报错 repositoryService.deleteDeployment(deploymentId); //设置为true,级联删除流程定义。即使该流程有流程实例启动,也可以删除 //repositoryService.deleteDeployment(deploymentId, true); }
- 有时候我们还需要查看bpmn和png文件,观察某个流程包含哪些步骤,可是对于不懂开发的业务人员来说,总不能直接去代码中去看吧!因此,我们可以把bpmn和png从数据库中下载下来,保存在指定的服务器路径下。然后在页面中,通过img标签的形式,把下载的图片展示出来,这样就很方便了。此处只讲解如何从数据库下载文件。(将下载好的文件展示在页面,大家自行解决)。
//下载bpmn和png文件 @Test public void queryBpmnFile() throws IOException { //得到引擎 ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); //获取repositoryService RepositoryService repositoryService = processEngine.getRepositoryService(); //得到查询器,设置查询条件,得到想要的流程定义 ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery() .processDefinitionKey("myLeave") .singleResult(); //通过流程定义信息,得到部署Id String deploymentId = processDefinition.getDeploymentId(); //通过repositoryService的方法,实现读取图片信息和bpmn信息 //png图片的流 InputStream pngInput = repositoryService.getResourceAsStream(deploymentId, processDefinition.getDiagramResourceName()); //bpmn文件的流 InputStream bpmnInput = repositoryService.getResourceAsStream(deploymentId, processDefinition.getResourceName()); //构造OutputStream的流 File file_png = new File("D:\\Leave.bpmn.png"); File file_bpmn = new File("D:\\Leave.bpmn"); FileOutputStream pngOut = new FileOutputStream(file_png); FileOutputStream bpmnOut = new FileOutputStream(file_bpmn); //输入流、输出流的转换 IOUtils.copy(pngInput, pngOut); IOUtils.copy(bpmnInput, bpmnOut); //关闭流 bpmnOut.close(); bpmnOut.close(); bpmnInput.close(); pngInput.close(); }
- 接下来我们介绍,怎么查看某个流程实例的执行情况,或者是某个流程下的所有实例的执行情况。这种功能很实用,我们可以把查询到的数据显示在页面,可以更直观地观察某个流程实例执行到了哪一步。关于查询流程的执行情况,这里提供了两种方式,第一种:如果查询条件中包含某个流程实例的id,那么就只查看这个流程实例的执行情况,这种情况更实用一些;第二种:如果查询条件中包含某个流程的定义id,那么就查看这个流程下所有流程实例的执行情况,这种情况或许不常用。
//查询某个流程实例或者某个流程下的所有实例的执行情况 @Test public void findHistoryInfo() { //得到引擎 ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); //获取historyService HistoryService historyService = processEngine.getHistoryService(); //获取actinst表的查询对象 HistoricActivityInstanceQuery instanceQuery = historyService.createHistoricActivityInstanceQuery(); //根据流程实例Id,查询单个流程实例的执行情况 instanceQuery.processInstanceId("5001"); //根据流程定义ID,查询该流程所有实例的执行情况 //instanceQuery.processDefinitionId("myLeave:1:2504"); instanceQuery.orderByHistoricActivityInstanceStartTime().asc(); //查询所有内容 List<HistoricActivityInstance> activityInstanceList = instanceQuery.list(); //输出 for (HistoricActivityInstance hi : activityInstanceList) { System.out.println(hi.getActivityId()); System.out.println(hi.getActivityName()); System.out.println(hi.getProcessDefinitionId()); System.out.println(hi.getProcessInstanceId()); System.out.println("-------------------------"); } }
- 总结:到目前为止,我们已经学习了activiti的基础操作,对于activiti数据库那几张常用的表大家最好能够知道,了解表结构后,出了问题也是容易排查的。另外,activiti虽然提供了工作流引擎的能力,但是在前端页面的展示功能,activiti是没有提供的,如果想要在页面更直观的查看流程的执行情况,还是要和前端交互。网上有不少activiti与前端整合的案例,大家可以参考,搭建属于自己的工作流框架。废话不多说,接下来我们介绍下activiti更高级的使用。
- 在开始前,大家需要明白两个概念,ProcessDefinition代表流程定义,ProcessInstance代表流程实例。流程定义ProcessDefinition是以BPMN文件定义的一个工作流程,是一组工作规范,例如我们之前定义的请假流程。流程实例ProcessInstance则是指一个具体的业务流程。例如某个员工发起一次请假,就会实例化一个请假的流程实例,并且每个不同的流程实例之间是互不影响的。另外,activiti是一个专注于工作流程的框架,它和我们的业务是完全分离的,可是在实际应用中,一个流程必须要依赖于系统业务,因此,我们就需要把流程与业务整合在一起,说白了,就是把业务id存入流程实例表中,形成对应关系。
//启动一个流程实例时,和业务绑定起来 @Test public void addBusinessKey() { //获取流程引擎 ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); //获取RuntimeService RuntimeService runtimeService = processEngine.getRuntimeService(); //启动流程实例的过程中,添加businesskey //第一个参数:流程定义的key 第二个参数:业务关键字 ProcessInstance instance = runtimeService.startProcessInstanceByKey("myLeave", "yeWuId"); //输出 System.out.println("businessKey:" + instance.getBusinessKey()); }
- 挂起、激活流程实例:有很多时候,我们需要暂时停止一个流程,过一段时间就要恢复。例如月底不接受报销审批流程,年底不接受借贷审批流程,或者非工作日不接受售后报销流程等,这个时候,就可以将流程进行挂起操作。挂起后的流程就不会再继续执行。
在挂起流程时,有两种操作方式:一种是挂起流程定义,此时该流程定义下的所有流程实例都不可执行;另一种是挂起某个流程实例,对其他流程实例是没有影响的。注意:如果流程定义或者流程实例挂起后,在挂起状态下推动流程实例是会抛出异常的。//全部流程实例的 挂起和激活 @Test public void suspendAllProcessInstance() { //获取流程引擎 ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); //获取repositoryService RepositoryService repositoryService = processEngine.getRepositoryService(); //查询流程定义,获取流程定义的查询对象 ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery() .processDefinitionKey("myLeave") .singleResult(); //查询当前流程定义的实例是否都是挂起状态,true表示已挂起,false表示未挂起 boolean suspended = processDefinition.isSuspended(); //获取流程定义的id String definitionId = processDefinition.getId(); //如果是挂起状态,改为激活状态 if (suspended) { //如果是挂起,可以执行激活的操作。参数1:流程定义id;参数2:是否激活;参数3:激活时间 repositoryService.activateProcessDefinitionById(definitionId, true, null); System.out.println("流程定义id:" + definitionId + ",已激活"); } else { //如果是激活状态,改为挂起状态。参数1:流程定义id;参数2:是否暂停;参数3:暂停时间 repositoryService.suspendProcessDefinitionById(definitionId, true, null); System.out.println("流程定义id:" + definitionId + ",已挂起"); } } //挂起和激活单个流程实例 @Test public void suspendSingleProcessInstance() { //获取流程引擎 ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); //runtimeService RuntimeService runtimeService = processEngine.getRuntimeService(); //查询流程实例,获取流程实例的查询对象 ProcessInstance processInstance = runtimeService.createProcessInstanceQuery() .processInstanceId("5001") .singleResult(); //查询当前流程定义的实例是否都是挂起状态,true表示已挂起,false表示未挂起 boolean suspended = processInstance.isSuspended(); //获取流程实例的id String instanceId = processInstance.getId(); //如果是挂起状态,改为激活状态 if (suspended) { //如果是挂起,可以执行激活的操作 runtimeService.activateProcessInstanceById(instanceId); System.out.println("流程实例id:" + instanceId + ",已激活"); } else { //如果是激活状态,改为挂起状态 runtimeService.suspendProcessInstanceById(instanceId); System.out.println("流程实例id:" + instanceId + ",已挂起"); } }
- 流程变量的概念
流程变量是Activiti中非常重要的角色。我们之前定义的请假流程并没有用到流程变量,每个步骤都是非常固定的,但是,当我们需要实现一些复杂的业务流程,比如出差3天以内由部门经理审批,3天以上需要增加总经理审批这样的流程时,就需要用到流程变量了。流程变量不仅可以动态控制流程走势,还能通过变量动态地设置任务节点的负责人,这种情况在实际工作中是很常见的。
注:流程变量和之前介绍的业务关键字其实是有些相似的,都可以携带业务信息。并且也都可以通过activiti的api查询出来。但是通常在使用过程中,应该尽量减少流程变量中的业务信息,这样能够减少业务代码对activiti工作流的代码侵入。
流程变量的类型是Map<String,Object>。变量值不仅可以是字符串,也可以是POJO对象。但是当需要将一个POJO对象放入流程变量时,要注意这个对象必须实现序列化接口Serializable。流程变量的作用域有2种:Global变量和Local变量。Global是流程变量的默认作用域,作用范围是整个流程实例,Global变量中的变量名不能重复,如果设置了相同的变量名,后面设置的值会覆盖之前设置的变量值。Local的作用域只针对一个任务(节点),Local变量名可以和Global变量名相同,不会有影响。
在介绍流程变量的使用方法之前,我们先定义一个有分支情况的请假流程,如下图所示。 - bpmn定义好流程之后,我们要使用代码,把流程部署到数据库。这个代码和之前部署流程的代码一样的。
//部署流程 @Test public void testDeployment() { //创建processEngine ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); //得到RepositoryService实例 RepositoryService repositoryService = processEngine.getRepositoryService(); //使用repositoryService进行部署 Deployment deployment = repositoryService.createDeployment() .addClasspathResource("bpmn/evection-global.bpmn")//添加bpmn资源 .name("出差申请流程-variables") .deploy(); //输出部署信息 System.out.println("流程部署Id:" + deployment.getId()); System.out.println("流程部署名称:" + deployment.getName()); }
- 流程部署完毕后,就可以开启一个流程实例。要注意,此处开启流程实例时,和之前不同,此处使用了流程变量,流程变量的值,与bpmn中设置了UEL表达式的地方是对应的。开启流程后,完成任务(推动节点)等操作就与之前的代码是一样的,这里就不再写出来了。
//启动实例 @Test public void testStartProcess() { //创建processEngine ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine(); //获取RunTimeService RuntimeService runtimeService = processEngine.getRuntimeService(); //流程定义的key String key = "myEvection"; //流程变量的map Map<String, Object> variables = new HashMap<String, Object>(); //设置流程变量 Evection evection = new Evection(); //出差2天。这记得bpmn中定义的${evection.num}吗?用于控制流程的执行逻辑 evection.setNum(2d);//这里的值如果大于等于3,就会走不同的分支 //通过evection.num来判断执行流程 variables.put("evection", evection); //bpmn文件中,使用uel定义的任务负责人。使用流程变量动态设置负责人 variables.put("assignee0", "李四"); variables.put("assignee1", "王经理"); variables.put("assignee2", "杨总经理"); variables.put("assignee3", "张财务"); //启动流程 runtimeService.startProcessInstanceByKey(key, variables); }
- 接下来我们学习网关。网关是用来控制流程流向的重要组件,通常都会结合流程变量来使用。 使用网关可以更好地控制流程走势,其优势比单纯使用连线的condition条件来控制走势更为显著。网关有4种类型:排他网关ExclusiveGateWay,并行网关ParallelGateway,包含网关InclusiveGateway和事件网关EventGateway,前三种网关比较常用,事件网关太过复杂,使用情况较少。下面分别介绍这几种网关:
排他网关
注意: 排他网关只会选择一个为true的分支执行。如果有两个分支条件都为true,排他网关会选择id值(节点的id)较小的一条分支渠执行。如果从网关出去的线所有条件都不满足则系统抛出异常,排他网关的图片如下所示:
这个流程定义和刚才定义的出差流程是一样的,只不过引入了网关这个组件。具体的流程定义细节如下所示,先从右侧组件栏拖出组件,画出各个流程节点,并给本次流程定义id和Name赋值:
然后点击流程节点,依次给流程节点的Name和Assignee属性赋值。此处的Assignee写死了,大家也可以使用流程变量,动态地设置节点负责人:
最后我们给连线添加判断条件来控制流程走势:
进行到这里,带网关的出差流程就定义完毕了。大家可以参考之前的代码,把定义的出差流程部署到数据库中,然后启动一个流程实例(不要忘记给evection.num赋值),并推动流程节点前进就可以了,此处就不做赘述了。
并行网关
并行网关允许将流程分成多条分支,也可以把多条分支汇聚到一起,并行网关的功能是基于进入和外出顺序流的:
fork分支:并行后的所有外出顺序流,为每个顺序流都创建一个并发分支。
join汇聚:所有到达并行网关,在此等待的进入分支,直到所有进入顺序流的分支都到达以后,流程才会通过汇聚网关。
注意:如果一个并行网关有多个进入和多个外出顺序流,它就同时具有分支和汇聚功能。这时,网关会先汇聚所有进入的顺序流,然后再切分成多个并行分支。与其他网关的主要区别是,并行网关不会解析条件。即使顺序流中定义了条件,也会被忽略。
包含网关
包含网关可以看做排他网关和并行网关的结合体。和排他网关一样,可以在外出顺序流上定义条件,包含网关会解析它们。但是主要区别在于包含网关可以选择多于一条顺序流,和并行网关一样。
包含网关的功能是基于进入和外出顺序流的:
分支:所有外出顺序流的条件都会被解析,结果为true的顺序流会以并行方式继续执行,会为每个顺序流创建一个分支。
汇聚:所有并行分支到达网关,会进入等待状态,直到每个包含流程token的进入顺序流的分支都到达。这是与并行网关的最大不同,换句话说,包含网关只会等待被选中执行了的进入顺序流。在汇聚之后,流程会穿过包含网关继续执行。
结语:到这里我们就把activiti常用的功能介绍完毕了,还有一些其他的功能没有讲到,比如一个节点设置多个候选人,候选人认领、归还和交接任务等,这些功能在后期后进行完善。
24.节点设置多个候选人
act_ru_identitylink。
因为处理人除了直接设置指定人处理的方式,act_ru_identitylink表没有数据,其他的方式都是在这个表有记录的,所以还是很有必要说一下这个表结构,表结构如下图所示:
act_ru_identitylink表结构说明:
task_id 对应act_ru_task中的主键id_
type_:对应类别有starter流程实例的发起者、candidate:节点的候选人
group_id:候选组的id主键,
user_id:对应人的id_主键
rev_:版本号
其中group_id、user_id不一定是activiti工作流引擎中人员组织关系,有可能使我们自定义的人员架构关系,这一点重点说明一下,因为后面的文章,我们会讲解如何扩展工作流人员架构
第一种方式assignee
xml
<userTask id="usertask1" name="请假审批" activiti:assignee="张三" >
<extensionElements>
</userTask>
我们运行的时候可以看到act_ru_task表中assignee_字段使我们设置的值
第二种方式assignee 变量方式
xml
<userTask id="usertask1" name="请假审批" activiti:assignee="${name}" >
<extensionElements>
</userTask>
运行的时候把当${name}"name变量传递进去,也就是把${}里面的变量传递进去就可以,我们运行的时候可以看到act_ru_task表中assignee_字段使我们设置的值,注意这个变量传递一定要在流程还没有到达这个任务节点的时候传递,否则当流程到达这个节点的时候,在传递变量就不行了。因为当前的节点设置的是下一个节点的变量值信息。
第三种方式 候选组的方式
xml
<userTask id="usertask1" name="请假审批" activiti:candidateUsers="a,b,c">
</userTask>
候选组使用的时候,多个就是,分割。比如我们需要设定当前的任务处理人是a、b、c三个人处理,那我们怎么设置呢?设置的时候,我们就设置为a,b,c即可。
当流程运转到当前的节点的时候a、b、c三个人都可以查询到代办信息,然后对应的操作。
数据库的变化就是放在act_ru_identitylink中多了三条记录分别对应同一个任务id。可以参考上面说的act_ru_identitylink描述。
变量候选组
xml
<userTask id="usertask1" name="请假审批" activiti:candidateUsers="${list}">
</userTask>
设置的时候我们可以指定一个集合,这个集合变量,我们在程序运行的时候,也是,分割的形式,进行传递值,不过需要注意,这种方式必须是一个集合对象,而不是一个字符串类型。
看下面的两种方式赋值:
第一种方式:
String candidateUsers="a,b,c";
task.setVariable("list", candidateUsers);
这种方式是错误的,程序运行的时候会报错,报错信息就是没有分享牛原创这个集合。
正确的方式如下:
String [] candidateUsers={"a","b","c"};
task.setVariable("list", Arrays.asList(candidateUsers));
数据库的变化就是放在act_ru_identitylink中多了三条记录分别对应同一个任务id。可以参考上面说的act_ru_identitylink描述。
第四种方式 角色的方式
多角色去处理,xml配置信息对应如下所示:
xml
<userTask id="usertask1" name="请假审批" activiti:candidateGroups="a,b,c">
</userTask>
角色使用的时候,多个就是,分割。比如我们需要设定当前的任务处理角色是a、b、c三个角色处理,那我们怎么设置呢?设置的时候,我们就设置为a,b,c即可。
当流程运转到当前的节点的时候a、b、c三个角色都可以查询到代办信息,然后对应的操作。
数据库的变化就是放在act_ru_identitylink中多了三条记录分别对应同一个任务id。可以参考上面说的act_ru_identitylink描述。
第五种方式 角色的方式 变量方式
xml
<userTask id="usertask1" name="请假审批" activiti:candidateGroups="${list}">
</userTask>
设置的时候我们可以指定一个集合,这个集合变量,我们在程序运行的时候,也是,分割的形式,进行传递值,不过需要注意,这种方式必须是一个集合对象,而不是一个字符串类型。
看下面的两种方式赋值:
String candidateUsers="a,b,c";
task.setVariable("分享牛原创", candidateUsers);
这种方式是错误的,程序运行的时候会报错,报错信息就是没有分享牛原创这个集合。
正确的方式如下:
String [] candidateUsers={"a","b","c"};
task.setVariable("list", Arrays.asList(candidateUsers));
第六种方式 监听器方式
监听器方式,我们可以动态的控制,在这个监听器中,我们可以从数据库中查询出来对应的人或者角色中的人,变量的方式设置进去,达到灵活多用。
具体的监听器的配置如下:
具体的xml如下:
<userTask id="usertask1" name="请假审批" >
<extensionElements>
<activiti:taskListener event="assignment" class="com.daling.bpmn.extend.MyTaskListener"></activiti:taskListener>
</extensionElements>
</userTask>
具体的监听实现类如下:
public class MyTaskListener implements TaskListener {
@Override
public void notify(DelegateTask task) {
String [] candidateUsers={"a","b","c"};
task.setVariable("list", Arrays.asList(candidateUsers));
}
}
总结:
activiti 任务节点处理人的设置总结如下:
1.指定具体的处理人,可以使用变量方式设置人。也可以直接在模板中定义。
2.候选组,可以在模版中定义,不灵活。灵活的做法,可以指定一个变量赋值。
3.角色,可以在模版中定义,不灵活。灵活的做法,可以指定一个变量赋值。
4.可以使用任务监听器的方式进行赋值操作。
5.变量的方式比较灵活,模板中直接定义不灵活,因为处理节点的人或者角色可能变化