工作流多实例
1.多实例介绍
多实例活动
是为业务流程中的某个步骤定义重复的一种方式。在编程概念中,多实例与 for each
结构相匹配:它允许对给定集合中的每个项目按顺序或并行地执行某个步骤或甚至一个完整的子流程。
多实例是一个有额外属性(所谓的 “多实例特性”)的常规活动,它将导致该活动在运行时被多次执行。以下活动可以成为多实例活动。
- Service Task 服务任务
- Send Task 发送任务
- User Task 用户任务
- Business Rule Task 业务规则任务
- Script Task 脚本任务
- Receive Task 接收任务
- Manual Task 手动任务
- (Embedded) Sub-Process (嵌入)子流程
- Call Activity 发起活动
- Transaction Subprocess 事务子流程
网关或事件不能成为多实例。
如果一个活动是多实例的,这将由活动底部的三条短线表示。三条垂直线表示实例将以并行方式执行,而三条水平线表示顺序执行。
按照规范的要求,每个实例所创建的执行的每个父执行将有以下变量:
- nrOfInstances: 实例的总数量
- nrOfActiveInstances: 当前活动的,即尚未完成的实例的数量。对于一个连续的多实例,这将永远是1。
- nrOfCompletedInstances: 已经完成的实例的数量。
这些值可以通过调用 “execution.getVariable(x) “方法检索。
此外,每个创建的执行将有一个执行本地变量(即对其他执行不可见,也不存储在流程实例级别)。
- loopCounter: 表示该特定实例的
for each
循环中的索引
为了使一个活动成为多实例,活动xml元素必须有一个multiInstanceLoopCharacteristics
子元素。
<multiInstanceLoopCharacteristics isSequential="false|true">
...
</multiInstanceLoopCharacteristics>
isSequential
属性表示该活动的实例是按顺序执行还是并行执行。
2. 基本应用
多实例应用中我们需要指的具体生成几个实例任务。指派的方式可以通过loopCardinality
属性来指的。通过loopCardinality
来指定既可以是固定值也可以指定表达式(只要结果是整数即可)
<multiInstanceLoopCharacteristics isSequential="false">
<loopCardinality>3</loopCardinality>
</multiInstanceLoopCharacteristics>
或者
<multiInstanceLoopCharacteristics isSequential="false">
<loopCardinality>${num}</loopCardinality>
</multiInstanceLoopCharacteristics>
如果我们需要同时指派多实例任务中的任务处理人。这时可以通过collection
和elementVariable
结合使用。
<multiInstanceLoopCharacteristics isSequential="false"
activiti:collection="${assigneeList}" activiti:elementVariable="assignal">
</multiInstanceLoopCharacteristics>
@Test
void startFlow(){
RuntimeService runtimeService = processEngine.getRuntimeService();
// 启动流程的同时我们需要绑定对应的流程变量
Map<String,Object> map = new HashMap<>();
map.put("assignList", Arrays.asList("张三","李四","王五"));
ProcessInstance processInstance = runtimeService
.startProcessInstanceById("muti-instance-02:1:347503",map);
String description = processInstance.getDescription();
System.out.println("description = " + description);
System.out.println("processInstance.getId() = " + processInstance.getId());
}
在多实例中我们也可以设置提前结束的条件,也就是不用等多实例中的每个实例都审批。通过completionCondition
属性就能达到我们的目的。比如审批的数量超过一半就通过结束。可以如下设置
<multiInstanceLoopCharacteristics isSequential="false" activiti:collection="${assigneeList}" activiti:elementVariable="assignal">
<completionCondition>${nrOfCompletedInstances/nrOfInstances >= 0.5 }</completionCondition>
</multiInstanceLoopCharacteristics>
上面表达式返回true
表示多实例结束也就是多实例中剩余的实例将被销毁,如果返回的是false
则继续等待剩余的实例完成相关的操作。为了更加灵活的控制多实例的结束。这块我们可以通过绑定Spring容器中的对象中的特定方法来处理。相关的多实例默认的变量存储在DelegateExecution
中,这块我们需要应用到。具体如下:
@Component("mutiInstanceDelegate")
public class MutiInstanceDelegate {
public boolean completeInstanceTask(DelegateExecution execution){
Integer nrOfInstances = (Integer) execution.getVariable("nrOfInstances");
// 当前活跃。即还未完成的
Integer nrOfActiveInstances = (Integer) execution.getVariable("nrOfActiveInstances");
Integer nrOfCompletedInstances = (Integer) execution.getVariable("nrOfCompletedInstances");
System.out.println("总的会签任务数量:" + nrOfInstances
+ "当前获取的会签任务数量:" + nrOfActiveInstances
+ " - " + "已经完成的会签任务数量:" + nrOfCompletedInstances);
return nrOfCompletedInstances > nrOfActiveInstances;
}
}
然后对应的配置为:
<multiInstanceLoopCharacteristics isSequential="false" activiti:collection="${assigneeList}" activiti:elementVariable="assignee">
<completionCondition>${mutiInstanceDelegate.completeInstanceTask(execution)}</completionCondition>
</multiInstanceLoopCharacteristics>
3.服务任务
上面的案例都是在用户任务
中实现。我们也可以在Service Task
来实现。具体如下:
具体的流程定义信息
<?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/processdef">
<process id="muti-instance-04-service-task" name="muti-instance-04-service-task" isExecutable="true">
<documentation>muti-instance-04-service-task</documentation>
<startEvent id="startEvent1"></startEvent>
<userTask id="sid-6E319DF5-9A30-4FB0-A4F8-619F9E90002C" name="用户任务"></userTask>
<sequenceFlow id="sid-10D69676-67F5-4514-A171-604748306FA5" sourceRef="startEvent1" targetRef="sid-6E319DF5-9A30-4FB0-A4F8-619F9E90002C"></sequenceFlow>
<sequenceFlow id="sid-D9FBD759-2872-40FA-8606-B993FE722E02" sourceRef="sid-6E319DF5-9A30-4FB0-A4F8-619F9E90002C" targetRef="sid-7E8B8F41-07EE-4439-9F76-4EBE1C77CE75"></sequenceFlow>
<endEvent id="sid-88E37A2E-33F5-4381-BC26-5A171D933F83"></endEvent>
<sequenceFlow id="sid-B135AB86-A96D-4423-A0A5-D221054AD366" sourceRef="sid-7E8B8F41-07EE-4439-9F76-4EBE1C77CE75" targetRef="sid-88E37A2E-33F5-4381-BC26-5A171D933F83"></sequenceFlow>
<serviceTask id="sid-7E8B8F41-07EE-4439-9F76-4EBE1C77CE75" name="服务任务" activiti:class="com.boge.activiti.delegate.MyMutiInstanceJavaDelegate">
<multiInstanceLoopCharacteristics isSequential="false" activiti:collection="${assignList}" activiti:elementVariable="userName">
<completionCondition>${myMutiInstanceDelegate.completeMutiInstanceTask(execution)}</completionCondition>
</multiInstanceLoopCharacteristics>
</serviceTask>
</process>
<bpmndi:BPMNDiagram id="BPMNDiagram_muti-instance-04-service-task">
<bpmndi:BPMNPlane bpmnElement="muti-instance-04-service-task" id="BPMNPlane_muti-instance-04-service-task">
<bpmndi:BPMNShape bpmnElement="startEvent1" id="BPMNShape_startEvent1">
<omgdc:Bounds height="30.0" width="30.0" x="100.0" y="163.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="sid-6E319DF5-9A30-4FB0-A4F8-619F9E90002C" id="BPMNShape_sid-6E319DF5-9A30-4FB0-A4F8-619F9E90002C">
<omgdc:Bounds height="80.0" width="100.0" x="175.0" y="138.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="sid-88E37A2E-33F5-4381-BC26-5A171D933F83" id="BPMNShape_sid-88E37A2E-33F5-4381-BC26-5A171D933F83">
<omgdc:Bounds height="28.0" width="28.0" x="465.0" y="164.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="sid-7E8B8F41-07EE-4439-9F76-4EBE1C77CE75" id="BPMNShape_sid-7E8B8F41-07EE-4439-9F76-4EBE1C77CE75">
<omgdc:Bounds height="80.0" width="100.0" x="320.0" y="138.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge bpmnElement="sid-B135AB86-A96D-4423-A0A5-D221054AD366" id="BPMNEdge_sid-B135AB86-A96D-4423-A0A5-D221054AD366">
<omgdi:waypoint x="420.0" y="178.0"></omgdi:waypoint>
<omgdi:waypoint x="465.0" y="178.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sid-D9FBD759-2872-40FA-8606-B993FE722E02" id="BPMNEdge_sid-D9FBD759-2872-40FA-8606-B993FE722E02">
<omgdi:waypoint x="275.0" y="178.0"></omgdi:waypoint>
<omgdi:waypoint x="320.0" y="178.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sid-10D69676-67F5-4514-A171-604748306FA5" id="BPMNEdge_sid-10D69676-67F5-4514-A171-604748306FA5">
<omgdi:waypoint x="130.0" y="178.0"></omgdi:waypoint>
<omgdi:waypoint x="175.0" y="178.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</definitions>
然后绑定的JavaDelegate为:
public class MyMutiInstanceJavaDelegate implements JavaDelegate {
@Override
public void execute(DelegateExecution execution) {
Object userName = execution.getVariable("userName");
Object loopCounter = execution.getVariable("loopCounter");
System.out.println("第"+loopCounter+"个,用户的名称是:"+ userName);
}
}
然后完成条件对应的方法为:
@Component("myMutiInstanceDelegate")
public class MyMutiInstanceDelegate {
public boolean completeMutiInstanceTask(DelegateExecution execution){
// 获取当前多实例中的相关的参数
// 总得流程实例数量
Integer nrOfInstances = (Integer) execution.getVariable("nrOfInstances");
// 当前活跃的实例数量【没有审批的数量】
Integer nrOfActiveInstances = (Integer) execution.getVariable("nrOfActiveInstances");
// 当前已经审批的数量
Integer nrOfCompletedInstances = (Integer) execution.getVariable("nrOfCompletedInstances");
System.out.println("nrOfInstances = " + nrOfInstances);
System.out.println("nrOfActiveInstances = " + nrOfActiveInstances);
System.out.println("nrOfCompletedInstances = " + nrOfCompletedInstances);
return nrOfCompletedInstances > nrOfActiveInstances;
}
}
然后流程流转到多实例节点的日志输出如下:
第0个,用户的名称是:张三2
nrOfInstances = 3
nrOfActiveInstances = 2
nrOfCompletedInstances = 1
第1个,用户的名称是:李四2
nrOfInstances = 3
nrOfActiveInstances = 1
nrOfCompletedInstances = 2
4.子流程应用
多实例的场景也可以在子流程中来使用,具体我们通过案例来讲解。本质上和我们前面介绍的是差不多的
完整的流程图
<?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/processdef">
<process id="muti-instance-05-sub-process" name="muti-instance-05-sub-process" isExecutable="true">
<documentation>muti-instance-05-sub-process</documentation>
<startEvent id="startEvent1"></startEvent>
<userTask id="sid-B2597779-A6FB-47DD-BD94-E31C499F8DBC" name="用户任务1"></userTask>
<sequenceFlow id="sid-E9764E43-33D7-4865-93EC-24F352B68978" sourceRef="startEvent1" targetRef="sid-B2597779-A6FB-47DD-BD94-E31C499F8DBC"></sequenceFlow>
<subProcess id="sid-EDCBFFC9-7B9C-4BE2-80BF-5346F4CE170A" name="部门审批流程">
<multiInstanceLoopCharacteristics isSequential="false" activiti:collection="${deptList}" activiti:elementVariable="dept">
<completionCondition>${myMutiInstanceDelegate.completeMutiInstanceTask(execution)}</completionCondition>
</multiInstanceLoopCharacteristics>
<startEvent id="sid-6DD60C57-8D17-46A4-8E43-6093EC636137"></startEvent>
<userTask id="sid-5075051C-77FD-40DA-8CDA-1EA1AF0771D3" name="领导审批"></userTask>
<userTask id="sid-D1DB857A-D45E-4A7C-9408-E0D9D5E002DB" name="助理盖章"></userTask>
<endEvent id="sid-7BD69F6C-70A4-4D76-BD22-3CC8E0C8FE8A"></endEvent>
<sequenceFlow id="sid-50D82417-8A82-46E5-89BF-4A74A115A846" sourceRef="sid-6DD60C57-8D17-46A4-8E43-6093EC636137" targetRef="sid-5075051C-77FD-40DA-8CDA-1EA1AF0771D3"></sequenceFlow>
<sequenceFlow id="sid-025AB6E4-A756-4241-A5D9-4F33147AB299" sourceRef="sid-5075051C-77FD-40DA-8CDA-1EA1AF0771D3" targetRef="sid-D1DB857A-D45E-4A7C-9408-E0D9D5E002DB"></sequenceFlow>
<sequenceFlow id="sid-40380635-A29E-4EAE-8778-F9145C237801" sourceRef="sid-D1DB857A-D45E-4A7C-9408-E0D9D5E002DB" targetRef="sid-7BD69F6C-70A4-4D76-BD22-3CC8E0C8FE8A"></sequenceFlow>
</subProcess>
<sequenceFlow id="sid-051CE217-E7E0-406C-A96A-9EBBC94E69D0" sourceRef="sid-B2597779-A6FB-47DD-BD94-E31C499F8DBC" targetRef="sid-EDCBFFC9-7B9C-4BE2-80BF-5346F4CE170A"></sequenceFlow>
<userTask id="sid-F361AC3E-A6C9-41B8-BE56-FB62E0AC0308" name="用户任务2"></userTask>
<sequenceFlow id="sid-7C5829FE-C2B4-4829-A585-7F504F30A768" sourceRef="sid-EDCBFFC9-7B9C-4BE2-80BF-5346F4CE170A" targetRef="sid-F361AC3E-A6C9-41B8-BE56-FB62E0AC0308"></sequenceFlow>
<endEvent id="sid-5C3B37EA-422C-4020-9015-59665363D7CB"></endEvent>
<sequenceFlow id="sid-685FAC99-C12A-4A27-9E73-53FFD43008D6" sourceRef="sid-F361AC3E-A6C9-41B8-BE56-FB62E0AC0308" targetRef="sid-5C3B37EA-422C-4020-9015-59665363D7CB"></sequenceFlow>
</process>
<bpmndi:BPMNDiagram id="BPMNDiagram_muti-instance-05-sub-process">
<bpmndi:BPMNPlane bpmnElement="muti-instance-05-sub-process" id="BPMNPlane_muti-instance-05-sub-process">
<bpmndi:BPMNShape bpmnElement="startEvent1" id="BPMNShape_startEvent1">
<omgdc:Bounds height="30.0" width="30.0" x="90.0" y="190.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="sid-B2597779-A6FB-47DD-BD94-E31C499F8DBC" id="BPMNShape_sid-B2597779-A6FB-47DD-BD94-E31C499F8DBC">
<omgdc:Bounds height="80.0" width="100.0" x="165.0" y="165.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="sid-EDCBFFC9-7B9C-4BE2-80BF-5346F4CE170A" id="BPMNShape_sid-EDCBFFC9-7B9C-4BE2-80BF-5346F4CE170A">
<omgdc:Bounds height="315.0" width="544.0" x="360.0" y="44.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="sid-6DD60C57-8D17-46A4-8E43-6093EC636137" id="BPMNShape_sid-6DD60C57-8D17-46A4-8E43-6093EC636137">
<omgdc:Bounds height="30.0" width="30.0" x="429.5" y="189.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="sid-5075051C-77FD-40DA-8CDA-1EA1AF0771D3" id="BPMNShape_sid-5075051C-77FD-40DA-8CDA-1EA1AF0771D3">
<omgdc:Bounds height="80.0" width="100.0" x="504.5" y="164.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="sid-D1DB857A-D45E-4A7C-9408-E0D9D5E002DB" id="BPMNShape_sid-D1DB857A-D45E-4A7C-9408-E0D9D5E002DB">
<omgdc:Bounds height="80.0" width="100.0" x="649.5" y="164.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="sid-7BD69F6C-70A4-4D76-BD22-3CC8E0C8FE8A" id="BPMNShape_sid-7BD69F6C-70A4-4D76-BD22-3CC8E0C8FE8A">
<omgdc:Bounds height="28.0" width="28.0" x="794.5" y="190.0"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="sid-F361AC3E-A6C9-41B8-BE56-FB62E0AC0308" id="BPMNShape_sid-F361AC3E-A6C9-41B8-BE56-FB62E0AC0308">
<omgdc:Bounds height="80.0" width="100.0" x="949.0" y="161.5"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape bpmnElement="sid-5C3B37EA-422C-4020-9015-59665363D7CB" id="BPMNShape_sid-5C3B37EA-422C-4020-9015-59665363D7CB">
<omgdc:Bounds height="28.0" width="28.0" x="1094.0" y="187.5"></omgdc:Bounds>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge bpmnElement="sid-50D82417-8A82-46E5-89BF-4A74A115A846" id="BPMNEdge_sid-50D82417-8A82-46E5-89BF-4A74A115A846">
<omgdi:waypoint x="459.5" y="204.0"></omgdi:waypoint>
<omgdi:waypoint x="504.5" y="204.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sid-685FAC99-C12A-4A27-9E73-53FFD43008D6" id="BPMNEdge_sid-685FAC99-C12A-4A27-9E73-53FFD43008D6">
<omgdi:waypoint x="1049.0" y="201.5"></omgdi:waypoint>
<omgdi:waypoint x="1094.0" y="201.5"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sid-E9764E43-33D7-4865-93EC-24F352B68978" id="BPMNEdge_sid-E9764E43-33D7-4865-93EC-24F352B68978">
<omgdi:waypoint x="120.0" y="205.0"></omgdi:waypoint>
<omgdi:waypoint x="165.0" y="205.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sid-051CE217-E7E0-406C-A96A-9EBBC94E69D0" id="BPMNEdge_sid-051CE217-E7E0-406C-A96A-9EBBC94E69D0">
<omgdi:waypoint x="265.0" y="204.58033573141486"></omgdi:waypoint>
<omgdi:waypoint x="360.0" y="203.7829736211031"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sid-7C5829FE-C2B4-4829-A585-7F504F30A768" id="BPMNEdge_sid-7C5829FE-C2B4-4829-A585-7F504F30A768">
<omgdi:waypoint x="904.0" y="201.5"></omgdi:waypoint>
<omgdi:waypoint x="949.0" y="201.5"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sid-025AB6E4-A756-4241-A5D9-4F33147AB299" id="BPMNEdge_sid-025AB6E4-A756-4241-A5D9-4F33147AB299">
<omgdi:waypoint x="604.5" y="204.0"></omgdi:waypoint>
<omgdi:waypoint x="649.5" y="204.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge bpmnElement="sid-40380635-A29E-4EAE-8778-F9145C237801" id="BPMNEdge_sid-40380635-A29E-4EAE-8778-F9145C237801">
<omgdi:waypoint x="749.5" y="204.0"></omgdi:waypoint>
<omgdi:waypoint x="794.5" y="204.0"></omgdi:waypoint>
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</definitions>
部署启动后。子流程作为多流程实例。会产生多个任务。只有子流程中的流程审批完成后才表示一个实例的完成。