目录
Activiti顺序流、网关、多实例及补偿处理详解
1、顺序流
顺序流主要用于连接流程中的两个元素,在流程图中,其主要用来展示流程的走向。BPMN规范对关于顺序流的连接也提供了连接规则,例如顺序流不能连接到开始事件,结束事件不能连接到另外的流程元素,子流程中的元素不能连接到子流程外的元素等。使用以下的配置定义一个顺序流:
<sequenceFlow id="flowId"sourceRef="fromId"targetRef="targetId"/>
使用sequenceFlow元素配置一个顺序流,sourceRef表示顺序流的源对象id,targetRef表示目标对象id,以上的配置表示顺序流从“fromId”元素连接到“targetId”元素。
BPMN中定义了两种顺序流:条件顺序流(Conditional outgoing Sequence Flow)和默认顺序流(Default outgoing Sequence Flow)。
1.1、条件顺序流
在顺序流中可以使用条件表达式,当表达式的计算结果为tue时,流程就向该顺序流执行,一般情况下,表达式所在的顺序流的源对象为网关或者流程活动。
如果一个条件顺序流从一个流程活动连接到另外的流程元素,那么除了这个顺序流外,应该至少再提供一个顺序流,如果只提供一个条件顺序流,那么有可能判定的条件不成立,导致流程毫无意义(无法向前执行),对于这种情况,Activiti会直接将流程结束,并不会抛出异常。
如果一个条件顺序流从一个网关连接到另外的流程元素,那么这个网关不能是并行网关或者事件网关。条件顺序流表示符合特定条件时,流程向该顺序流前进,而并行网关则表示流程会同时进行,这明显与条件顺序流相违背,而事件网关的触发是以事件为条件的,因此和条件顺序流相冲突,所以条件顺序流的源对象是网关的话,不能为这两种类型的网关。
<process id="process1" name="process1" isExecutable="true">
<startEvent id="startevent1" name="Start"></startEvent>
<userTask id="usertask1" name="填写请假申请"></userTask>
<exclusiveGateway id="exclusivegateway1" name="Exclusive Gateway"></exclusiveGateway>
<userTask id="usertask2" name="部门经理审批"></userTask>
<userTask id="usertask3" name="总经理审批"></userTask>
<endEvent id="endevent1" name="End"></endEvent>
<sequenceFlow id="flow1" sourceRef="usertask2" targetRef="endevent1"></sequenceFlow>
<sequenceFlow id="flow2" sourceRef="startevent1"
targetRef="usertask1"></sequenceFlow>
<sequenceFlow id="flow3" sourceRef="usertask1" targetRef="exclusivegateway1"></sequenceFlow>
<sequenceFlow id="flow4" name="小于等于5天" sourceRef="exclusivegateway1"
targetRef="usertask2">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[
${days <= 5}
]]></conditionExpression>
</sequenceFlow>
<sequenceFlow id="flow5" name="大于5天" sourceRef="exclusivegateway1"
targetRef="usertask3">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[
${days > 5}
]]></conditionExpression>
</sequenceFlow>
<sequenceFlow id="flow6" sourceRef="usertask3" targetRef="endevent1"></sequenceFlow>
</process>
代码定义了两个条件顺序流,第一个条件顺序流从“填写请
假申请”任务连接到“部门经理审批”任务,这个顺序流的执行条件是参数“dys”小于等于5。第二个条件顺序流从“填写请假申请”任务连接到“总经理审批”任务,执行条件是参数
“days”大于5。在配置顺序流的条件时,使用了conditionlExpression元素,并使用xsi:type属性来配置表达式的类型,当前Activiti只支持“tFormalExpression”类型的表达式,即Activiti中的UEL表达式。
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
RepositoryService repositoryService = engine.getRepositoryService();
RuntimeService runtimeService = engine.getRuntimeService();
TaskService taskService = engine.getTaskService();
repositoryService.createDeployment().addClasspathResource("demo13/ConditionalSequenceFlow.bpmn").deploy();
// 初始化参数
Map<String, Object> vars = new HashMap<String, Object>();
vars.put("days", 6);
ProcessInstance pi = runtimeService.startProcessInstanceByKey("process1", vars);
// 查询当前任务
Task task = taskService.createTaskQuery().processInstanceId(pi.getId()).singleResult();
System.out.println("当前任务:" + task.getName());
taskService.complete(task.getId());
// 查询第二个任务
task = taskService.createTaskQuery().processInstanceId(pi.getId()).singleResult();
System.out.println("当前任务:" + task.getName());
当前任务:填写请假申请
当前任务:总经理审批
1.2、默认顺序流
一个以单向(Exclusive)网关、兼容(Inclusive)网关、组合(Complex)网关或者流程活动作为源连接对象的顺序流,可以被定义为默认顺序流。在上一节中讲解了条件顺序流,如果各个条件顺序流都不符合执行条件,那么流程将不会向前进行,甚至在某些情况下会抛出异常,为解决该问题,可以为这些源连接对象设置默认顺序流。
<process id="process1" name="process1" isExecutable="true">
<startEvent id="startevent1" name="Start"></startEvent>
<userTask id="usertask1" name="填写请假申请" default="flow1"></userTask>
<userTask id="usertask2" name="部门经理审批"></userTask>
<userTask id="usertask3" name="总经理审批"></userTask>
<sequenceFlow id="flow1" sourceRef="usertask1" targetRef="usertask2"></sequenceFlow>
<sequenceFlow id="flow2" sourceRef="usertask1" targetRef="usertask3">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[
${days > 5}
]]></conditionExpression>
</sequenceFlow>
<sequenceFlow id="flow3" sourceRef="startevent1"
targetRef="usertask1"></sequenceFlow>
<endEvent id="endevent1" name="End"></endEvent>
<sequenceFlow id="flow4" sourceRef="usertask2" targetRef="endevent1"></sequenceFlow>
<sequenceFlow id="flow5" sourceRef="usertask3" targetRef="endevent1"></sequenceFlow>
</process>
代码中“填写请假申请”的用户任务,该任务被配置了default属性,其值指向定义的顺序流,表示当其他顺序流不符合条件时,将会执行该顺序流。在本例中,默认情况下,流程将会到达“部门经理审批”流程任务。当“dys”参数大于5时,流程将会到达“总经理审批”用户任务。
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
RepositoryService repositoryService = engine.getRepositoryService();
RuntimeService runtimeService = engine.getRuntimeService();
TaskService taskService = engine.getTaskService();
repositoryService
.createDeployment()
.addClasspathResource("demo13/DefaultSequenceFlow.bpmn")
.deploy();
Map<String, Object> vars = new HashMap<String, Object>();
vars.put("days", 5);
ProcessInstance pi = runtimeService.startProcessInstanceByKey("process1", vars);
// 查询并完成任务
Task task = taskService.createTaskQuery().processInstanceId(pi.getId()).singleResult();
System.out.println("当前任务:" + task.getName());
taskService.complete(task.getId());
// 查询当前任务
task = taskService.createTaskQuery().processInstanceId(pi.getId()).singleResult();
System.out.println("当前任务:" + task.getName());
当前任务:填写请假申请
当前任务:部门经理审批
2、流程网关
流程中的网关(Gateway)主要用于在流程中控制顺序流的分支与汇合,如果不需要进行这些顺序流控制,那么可以不使用网关,BPMN规范描述网关能够消耗执行流,或者产生执行流。BPMN规范定义了五种网关:单向网关、并行网关、兼容网关、事件网关和组合网关,当前版本的Activiti不支持组合网关,因此本书不涉及组合网关的内容。
2.1、单向网关
单向(Exclusive)网关就好像一个人在分岔路口,只能选择一条路前进,而如何选择前进的路,由条件决定。由于只有一条执行流向前执行,并且可能存在多个条件顺序流都符合条件的情况,因此对于这种情况,会选择第一个在流程文件中定义的条件顺序流,如果没有符合条件的顺序流,将会抛出异常。
<process id="process1" name="process1" isExecutable="true">
<startEvent id="startevent1" name="Start"></startEvent>
<userTask id="usertask1" name="Task 1"></userTask>
<userTask id="usertask2" name="Task A"></userTask>
<userTask id="usertask4" name="Task B"></userTask>
<userTask id="usertask3" name="Task 2"></userTask>
<userTask id="usertask5" name="Task 3"></userTask>
<userTask id="usertask6" name="Task C"></userTask>
<endEvent id="endevent1" name="End"></endEvent>
<exclusiveGateway id="exclusivegateway3" name="Exclusive Gateway"></exclusiveGateway>
<exclusiveGateway id="exclusivegateway1" name="Exclusive Gateway"></exclusiveGateway>
<exclusiveGateway id="exclusivegateway2" name="Exclusive Gateway"></exclusiveGateway>
<sequenceFlow id="flow2" name="大于等于5" sourceRef="exclusivegateway1" targetRef="usertask1">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${days >= 5}]]></conditionExpression>
</sequenceFlow>
<sequenceFlow id="flow4" name="大于等于10" sourceRef="exclusivegateway2" targetRef="usertask3">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${days >= 10}]]></conditionExpression>
</sequenceFlow>
<sequenceFlow id="flow13" name="等于10" sourceRef="exclusivegateway2" targetRef="usertask4">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${days == 10}]]></conditionExpression>
</sequenceFlow>
<sequenceFlow id="flow5" sourceRef="usertask3" targetRef="exclusivegateway3"></sequenceFlow>
<sequenceFlow id="flow6" name="大于等于20" sourceRef="exclusivegateway3" targetRef="usertask5">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${days >= 20}]]></conditionExpression>
</sequenceFlow>
<sequenceFlow id="flow7" sourceRef="usertask5" targetRef="endevent1"></sequenceFlow>
<sequenceFlow id="flow8" name="小于5" sourceRef="exclusivegateway1" targetRef="usertask2">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${days < 5}]]></conditionExpression>
</sequenceFlow>
<sequenceFlow id="flow12" name="小于等于10" sourceRef="exclusivegateway3" targetRef="usertask6">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[${days <= 10}]]></conditionExpression>
</sequenceFlow>
<sequenceFlow id="flow3" sourceRef="usertask1" targetRef="exclusivegateway2"></sequenceFlow>
<sequenceFlow id="flow1" sourceRef="startevent1" targetRef="exclusivegateway1"></sequenceFlow>
<sequenceFlow id="flow9" sourceRef="usertask2" targetRef="endevent1"></sequenceFlow>
<sequenceFlow id="flow10" sourceRef="usertask4" targetRef="endevent1"></sequenceFlow>
<sequenceFlow id="flow11" sourceRef="usertask6" targetRef="endevent1"></sequenceFlow>
</process>
图中的流程有三个单向网关,在第一个单向网关处,当流程参数大于等于5时,流程会选择到达“Task1”。在第二个网关处,当流程参数等于10时,连接到“Task2”和“Task
B”的顺序流均符合条件,但是由于在XML文件中(代码清单13-18),连接到“Tsk2”的顺序流比连接到“TskB”的顺序流先定义,因此流程会到达“Tsk2”。在第三个网关处,当流程参数介于10和20之间时,就会找不到相应的顺序流,因此会抛出异常。
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
RepositoryService repositoryService = engine.getRepositoryService();
RuntimeService runtimeService = engine.getRuntimeService();
TaskService taskService = engine.getTaskService();
repositoryService
.createDeployment()
.addClasspathResource("demo13/Exclusive.bpmn")
.deploy();
// 初始化参数
Map<String, Object> vars = new HashMap<String, Object>();
vars.put("days", 6);
ProcessInstance pi = runtimeService.startProcessInstanceByKey("process1", vars);
// 查询当前任务
Task task = taskService.createTaskQuery().processInstanceId(pi.getId()).singleResult();
System.out.println("当前任务:" + task.getName());
//完成任务,设置参数为10
vars.put("days", 10);
taskService.complete(task.getId(), vars);
// 查询当前任务
task = taskService.createTaskQuery().processInstanceId(pi.getId()).singleResult();
System.out.println("当前任务:" + task.getName());
// 完成任务,设置参数为15
vars.put("days", 15);
// 此时会抛出异常
taskService.complete(task.getId(), vars);
当前任务:Task 1
当前任务:Task 2
org.activiti.engine.ActivitiException: No outgoing sequence flow of the exclusive gateway 'exclusivegateway3' could be selected for continuing the process
根据输出的结果可知,执行流会先到达“Task1”,再到达“Task2”,当到达第三个单向网关时,因找不到符合条件的顺序流,所以抛出异常。
2.2、并行网关
并行网关(Parallel Gateway)用于表示流程的并发,并行网关可以让一个执行流变为多个同时进行的并发执行流,也可以让多个执行流合并为一个执行流,因此并行网关对执行流会有两种行为:分岔(fork)与合并(join)。分岔表示为每条从并行网关出来的顺序流建立一个并行的执行流,合并表示所有到达并行网关的并行执行流将会被合并。
需要注意的是,同一个并行网关,允许同时出现分岔和合并两种行为,当多个执行流到达这种并行网关时,并行网关会先将这些执行流合并,然后再进行分岔,如图13-14所示。
除此之外,并行网关与单向网关不一样的是,并行网关并不关心顺序流的条件,定义在顺序流中的条件会被忽略,并行网关会根据实际情况产生相应数量的执行流。定义一个并行网关,使用parallelGateway元素:
<parallelGateway id="parallelgateway2"name="Parallel Gateway"></parallelGateway>
下图所示为一个含有并行网关的请假流程:
<process id="process1" name="process1" isExecutable="true">
<startEvent id="startevent1" name="Start"></startEvent>
<userTask id="usertask1" name="填写申请"></userTask>
<parallelGateway id="parallelgateway1" name="Parallel Gateway"></parallelGateway>
<userTask id="usertask2" name="人事审批"></userTask>
<userTask id="usertask3" name="总监审批"></userTask>
<parallelGateway id="parallelgateway2" name="Parallel Gateway"></parallelGateway>
<userTask id="usertask4" name="总经理审批"></userTask>
<endEvent id="endevent1" name="End"></endEvent>
<sequenceFlow id="flow1" sourceRef="startevent1"
targetRef="usertask1"></sequenceFlow>
<sequenceFlow id="flow2" sourceRef="usertask1" targetRef="parallelgateway1"></sequenceFlow>
<sequenceFlow id="flow3" sourceRef="parallelgateway1"
targetRef="usertask2"></sequenceFlow>
<sequenceFlow id="flow4" sourceRef="usertask2" targetRef="parallelgateway2"></sequenceFlow>
<sequenceFlow id="flow5" sourceRef="parallelgateway2"
targetRef="usertask4"></sequenceFlow>
<sequenceFlow id="flow6" sourceRef="usertask4" targetRef="endevent1"></sequenceFlow>
<sequenceFlow id="flow7" sourceRef="parallelgateway1"
targetRef="usertask3"></sequenceFlow>
<sequenceFlow id="flow8" sourceRef="usertask3" targetRef="parallelgateway2"></sequenceFlow>
</process>
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
RepositoryService repositoryService = engine.getRepositoryService();
RuntimeService runtimeService = engine.getRuntimeService();
TaskService taskService = engine.getTaskService();
repositoryService.createDeployment().addClasspathResource("demo13/Parallel.bpmn").deploy();
// 启动流程
ProcessInstance pi = runtimeService.startProcessInstanceByKey("process1");
// 完成填写申请任务并设置参数
Map<String, Object> vars = new HashMap<String, Object>();
vars.put("days", 6);
Task task = taskService.createTaskQuery().processInstanceId(pi.getId()).singleResult();
taskService.complete(task.getId(), vars);
// 查询执行流数量
List<Execution> exes = runtimeService.createExecutionQuery().processInstanceId(pi.getId()).list();
System.out.println("当前的执行流数量:" + exes.size());
// 完成人事审批任务
task = taskService.createTaskQuery().processInstanceId(pi.getId()).taskName("人事审批").singleResult();
System.out.println("当前任务:" + task.getName());
taskService.complete(task.getId(), vars);
// 查询执行流数量
exes = runtimeService.createExecutionQuery().processInstanceId(pi.getId()).list();
System.out.println("当前执行流数量:" + exes.size());
// 完成总监审批任务
task = taskService.createTaskQuery().processInstanceId(pi.getId()).taskName("总监审批").singleResult();
System.out.println("当前任务:" + task.getName());
taskService.complete(task.getId(), vars);
// 查询执行流数量
exes = runtimeService.createExecutionQuery().processInstanceId(pi.getId()).list();
System.out.println("当前执行流数量:" + exes.size());
当前的执行流数量:3
当前任务:人事审批
当前执行流数量:3
当前任务:总监审批
当前执行流数量:2
2.3、兼容网关
兼容网关(Inclusive)就好像单向网关与并行网关的结合体,可以为兼容网关的输出顺序流定义条件,如果其中一条顺序流符合条件,就会创建一条新的执行流。与单向网关不同的是,兼容网关可以创建多条执行流,换言之,如果多个条件都成立,那么此时就可以将兼容网关看作并行网关,而在单向网关中,即使多个条件都符合,也只会选择第一个定义的顺序流。相对于单向网关和并行网关,兼容网关显得更加灵活,在实际应用中,由于一些无法确认的条件而导致无法在单向网关和并行网关中做出选择时,可以使用兼容网关。
当兼容网关作为并行网关使用时,它同样拥有并行网关的特性,一样会有分岔(fork)与合并(join)两个行为,并且处理方式也与并行网关一致。将上图所示的请假流程稍做修改,将并行网关改为兼容网关,新流程如下图所示:
<process id="process1" name="process1" isExecutable="true">
<startEvent id="startevent1" name="Start"></startEvent>
<userTask id="usertask1" name="填写申请"></userTask>
<inclusiveGateway id="inclusivegateway1" name="Inclusive Gateway"></inclusiveGateway>
<userTask id="usertask2" name="人事审批"></userTask>
<userTask id="usertask3" name="总监审批"></userTask>
<inclusiveGateway id="inclusivegateway2" name="Inclusive Gateway"></inclusiveGateway>
<userTask id="usertask4" name="总经理审批"></userTask>
<endEvent id="endevent1" name="End"></endEvent>
<sequenceFlow id="flow1" sourceRef="startevent1"
targetRef="usertask1"></sequenceFlow>
<sequenceFlow id="flow2" sourceRef="usertask1" targetRef="inclusivegateway1"></sequenceFlow>
<sequenceFlow id="flow3" name="大于等于3" sourceRef="inclusivegateway1"
targetRef="usertask2">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[
${days >= 3}
]]></conditionExpression>
</sequenceFlow>
<sequenceFlow id="flow7" name="大于等于10" sourceRef="inclusivegateway1"
targetRef="usertask3">
<conditionExpression xsi:type="tFormalExpression"><![CDATA[
${days >= 10}
]]></conditionExpression>
</sequenceFlow>
<sequenceFlow id="flow4" sourceRef="usertask2" targetRef="inclusivegateway2"></sequenceFlow>
<sequenceFlow id="flow5" sourceRef="inclusivegateway2"
targetRef="usertask4"></sequenceFlow>
<sequenceFlow id="flow6" sourceRef="usertask4" targetRef="endevent1"></sequenceFlow>
<sequenceFlow id="flow8" sourceRef="usertask3" targetRef="inclusivegateway2"></sequenceFlow>
</process>
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
RepositoryService repositoryService = engine.getRepositoryService();
RuntimeService runtimeService = engine.getRuntimeService();
TaskService taskService = engine.getTaskService();
repositoryService.createDeployment().addClasspathResource("demo13/Inclusive.bpmn").deploy();
// 初始化参数
Map<String, Object> vars = new HashMap<String, Object>();
vars.put("days", 6);
ProcessInstance pi1 = runtimeService.startProcessInstanceByKey("process1", vars);
// 完成填写申请任务
Task task = taskService.createTaskQuery().processInstanceId(pi1.getId()).singleResult();
System.out.println("当前任务:" + task.getName());
taskService.complete(task.getId());
// 查询执行流
List<Execution> exes = runtimeService.createExecutionQuery().processInstanceId(pi1.getId()).list();
System.out.println("参数为6时执行流数量:" + exes.size());
// 完成全部任务
List<Task> tasks = taskService.createTaskQuery().processInstanceId(pi1.getId()).list();
for (Task taskObj : tasks) {
taskService.complete(taskObj.getId());
}
// 完成总经理审批任务
task = taskService.createTaskQuery().processInstanceId(pi1.getId()).singleResult();
System.out.println("当前任务:" + task.getName());
taskService.complete(task.getId());
System.out.println("再次启动流程=============");
// 再次启动流程,参数为10
vars.put("days", 10);
ProcessInstance pi2 = runtimeService.startProcessInstanceByKey("process1", vars);
// 完成填写申请任务
task = taskService.createTaskQuery().processInstanceId(pi2.getId()).singleResult();
System.out.println("当前任务:" + task.getName());
taskService.complete(task.getId());
// 查询执行流
exes = runtimeService.createExecutionQuery().processInstanceId(pi2.getId()).list();
System.out.println("参数为10时执行流数量:" + exes.size());
当前任务:填写申请
参数为6时执行流数量:2
当前任务:总经理审批
再次启动流程=============
当前任务:填写申请
参数为10时执行流数量:3
2.4、事件网关
在流程中如果需要根据事件来决定流程的走向,可以使用事件网关(Event Based Gateway)。事件网关会根据它所连接的中间Catching事件来决定流程的走向。事件网关看上去存在流程走向的选择问题,但是实际上,执行流并不会通过从事件网关出来的顺序流,当流程到达事件网关时,Activiti会为全部的中间Catching事件创建相应的数据,如果某一事件先被触发,那么流程将会往该事件所处的方向执行。
<signal id="mySignal" name="mySignal"></signal>
<process id="process1" name="process1" isExecutable="true">
<startEvent id="startevent1" name="Start"></startEvent>
<eventBasedGateway id="eventgateway1" name="Event Gateway"></eventBasedGateway>
<userTask id="usertask1" name="高级工程师"></userTask>
<intermediateCatchEvent id="signalintermediatecatchevent1"
name="SignalCatchEvent">
<signalEventDefinition signalRef="mySignal"></signalEventDefinition>
</intermediateCatchEvent>
<userTask id="usertask2" name="初始工程师"></userTask>
<intermediateCatchEvent id="timerintermediatecatchevent1"
name="TimerCatchEvent">
<timerEventDefinition>
<timeDuration>PT5S</timeDuration>
</timerEventDefinition>
</intermediateCatchEvent>
<endEvent id="endevent1" name="End"></endEvent>
<sequenceFlow id="flow1" sourceRef="startevent1"
targetRef="eventgateway1"></sequenceFlow>
<sequenceFlow id="flow2" sourceRef="eventgateway1"
targetRef="signalintermediatecatchevent1"></sequenceFlow>
<sequenceFlow id="flow3" sourceRef="signalintermediatecatchevent1"
targetRef="usertask1"></sequenceFlow>
<sequenceFlow id="flow6" sourceRef="eventgateway1"
targetRef="timerintermediatecatchevent1"></sequenceFlow>
<sequenceFlow id="flow9" sourceRef="timerintermediatecatchevent1"
targetRef="usertask2"></sequenceFlow>
<sequenceFlow id="flow10" sourceRef="usertask1"
targetRef="endevent1"></sequenceFlow>
<sequenceFlow id="flow11" sourceRef="usertask2"
targetRef="endevent1"></sequenceFlow>
</process>
流程开始时会经过一个事件网关,这个事件网关会连接到一个信号中间Catching事件和一个定时器中间Catching事件,根据第I1章的内容可知,中间Catching事件需要由特定的条件触发,本例中的信号中间Catching事件触发条件是接收到相应的信号,定时器中间Catching事件的触发由其定义的时间元素所决定。
如果信号事件被触发,则由“高级工程师”处理,如果定时器事件被触发,则由“初级工程师”处理。使用eventBasedGateway元素来配置事件网关,该网关连接到一个信号事件和一个定时器事件,定时器事件将在5s后被触发,在这个过程中,如果信号事件没有被触发,则由“初级工程师”处理,否则将由“高级工程师”处理:
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
RepositoryService repositoryService = engine.getRepositoryService();
RuntimeService runtimeService = engine.getRuntimeService();
TaskService taskService = engine.getTaskService();
repositoryService.createDeployment().addClasspathResource("demo13/EventBased.bpmn").deploy();
// 启动流程
ProcessInstance pi = runtimeService.startProcessInstanceByKey("process1");
// 发送消息
runtimeService.signalEventReceived("mySignal");
// 查询当前任务
Task task = taskService.createTaskQuery().processInstanceId(pi.getId()).singleResult();
System.out.println("当前任务:" + task.getName());
// 完成任务,结束流程
taskService.complete(task.getId());
// 重新启动流程
ProcessInstance pi2 = runtimeService.startProcessInstanceByKey("process1");
System.out.println("第二次启动流程");
// 暂停10秒,等待定时器事件触发
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 查询当前任务
task = taskService.createTaskQuery().processInstanceId(pi2.getId()).singleResult();
System.out.println("当前任务:" + task.getName())
当前任务:高级工程师
第二次启动流程
java.lang.NullPointerException
在第一次启动流程时,使用了signalEventReceived方法(代码清单中的粗体字代码),则流程中的信号中间Catching事件会被触发,此时查询流程任务,得到的结果将会是“高级工程师”,第二次启动流程时暂停10s,让流程中的定时器事件触发,最后查询当前任务,得到的结果是“初级工程师”,根据得到的结果可知,事件网关只会选择最先触发的事件所在的分支向前执行。
3、流程活动特性
流程活动包括嵌入式子流程(SubProcess)、流程任务(Task)和调用式子流程(CallActivity),这些流程活动已经在前面的章节中讲解过。关于这些流程活动共有的一些特性,本节将讲解,例如多实例活动和补偿处理者。
3.1、多实例活动
如果想让某些特定的流程活动的行为执行多次,则可以将活动设置为多实例,让其按照配置来执行相应的次数。流程活动都会有自己的行为,例如流程到达Service Task,会创建这个Service Task并且执行相应的Java类,且自动让流程通过该活动。User Task会创建活动的实例,流程是否通过User Task,由用户决定,这不属于活动的行为。活动的多实例可以让一个流程活动(甚至是子流程)按顺序或者同时执行,在执行的过程中,会为活动产生多个实例,因此
称该流程活动为多实例的流程活动。大部分的流程活动均可以被设置为循环执行,这些活动包括大部分的流程任务、嵌入式子流程和调用式子流程。为流程活动的元素添加multiInstanceLoopCharacteristics子元素,将该流程活动配置为一个多实例的活动,如以下的配置片断:
<userTask id="myTask">
<multiInstanceLoopCharacteristics isSequential="falseltrue">
</multiInstanceLoopCharacteristics>
</userTask>
以上配置片断中的multiInstanceLoopCharacteristics元素的isSequential属性,用于配置这个流程活动在执行时,是按顺序执行还是同时执行,为rue表示按顺序执行,false则同时执行。
多实例活动像是循环体,可以为它提供集合或者设定循环次数,让它循环,就像使用Java里面的循环语句一样。
<process id="process1" name="process1" isExecutable="true">
<startEvent id="startevent1" name="Start"></startEvent>
<userTask id="usertask2" name="Task 1">
<multiInstanceLoopCharacteristics
isSequential="true">
<loopCardinality>2</loopCardinality>
</multiInstanceLoopCharacteristics>
</userTask>
<userTask id="usertask3" name="Task 2">
<multiInstanceLoopCharacteristics
isSequential="false">
<loopCardinality>2</loopCardinality>
</multiInstanceLoopCharacteristics>
</userTask>
<endEvent id="endevent1" name="End"></endEvent>
<sequenceFlow id="flow1" sourceRef="startevent1"
targetRef="usertask2"></sequenceFlow>
<sequenceFlow id="flow2" sourceRef="usertask2" targetRef="usertask3"></sequenceFlow>
<sequenceFlow id="flow3" sourceRef="usertask3" targetRef="endevent1"></sequenceFlow>
</process>
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
RepositoryService repositoryService = engine.getRepositoryService();
RuntimeService runtimeService = engine.getRuntimeService();
TaskService taskService = engine.getTaskService();
repositoryService.createDeployment().addClasspathResource("demo13/SimpleMultiInstance.bpmn").deploy();
// 启动流程
ProcessInstance pi = runtimeService.startProcessInstanceByKey("process1");
// 查询执行流
List<Execution> exes = runtimeService.createExecutionQuery().processInstanceId(pi.getId()).list();
System.out.println("使用顺序执行的多实例活动,流程数量:" + exes.size());
// 完成全部任务
for (int i = 0; i < 2; i++) {
Task task = taskService.createTaskQuery().processInstanceId(pi.getId()).singleResult();
System.out.println("任务id:" + task.getId());
taskService.complete(task.getId());
}
// 流程到达第二个多实例活动(并行的)
exes = runtimeService.createExecutionQuery().processInstanceId(pi.getId()).list();
System.out.println("使用并行执行的多实例活动,流程数量:" + exes.size())
使用顺序执行的多实例活动,流程数量:3
任务id:14
任务id:16
使用并行执行的多实例活动,流程数量:4
需要注意的是,在循环完成第一个用户任务时会将这些用户任务实例数据的id输出,可以看到,这些任务的id均为不同的值,证明每一个用户任务均是一个单独的实例。另外,由于User Task的活动行为是创建活动的实例,并不会让流程通过该活动,因此需要让外部(代码清单中的粗体字代码)完成任务,让流程继续向前执行。
3.2、设置循环数据
在上一节的例子中,设置用户任务的循环次数为2,除此之外,还可以为循环提供集合数据,让该活动按照集合的大小(或者与之相关的数量)进行循环,集合可以使用BPMN规范提供的元素配置,也可以使用Activiti的扩展特性配置。使用BPMN规范方式配置的话,需要为multiInstanceLoopCharacteristics
添加loopDataInputRef
子元素,使用Activiti方式指定集合的话,需要为multiInstanceLoopCharacteristics元素添加activiti:collection
属性。使用loopDataInputRef子元素,需要将集合设置到流程中,而使用activiti:collection属性,则可以使用表达式,既可以从流程参数中获取集合,也可以直接调用对象的方法来获取。
<process id="process1" name="process1" isExecutable="true">
<startEvent id="startevent1" name="Start"></startEvent>
<userTask id="usertask1" name="Task 1">
<multiInstanceLoopCharacteristics
isSequential="false" activiti:collection="datas1"></multiInstanceLoopCharacteristics>
</userTask>
<userTask id="usertask2" name="Task 2">
<multiInstanceLoopCharacteristics
isSequential="false" activiti:collection="${datas2}"></multiInstanceLoopCharacteristics>
</userTask>
<endEvent id="endevent1" name="End"></endEvent>
<sequenceFlow id="flow1" sourceRef="startevent1"
targetRef="usertask1"></sequenceFlow>
<sequenceFlow id="flow2" sourceRef="usertask1" targetRef="usertask2"></sequenceFlow>
<sequenceFlow id="flow3" sourceRef="usertask2" targetRef="endevent1"></sequenceFlow>
</process>
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
RepositoryService repositoryService = engine.getRepositoryService();
RuntimeService runtimeService = engine.getRuntimeService();
TaskService taskService = engine.getTaskService();
repositoryService.createDeployment().addClasspathResource("demo13/DataMultiInstance.bpmn").deploy();
// 初始化参数
Map<String, Object> vars = new HashMap<String, Object>();
List<String> datas1 = new ArrayList<String>();
datas1.add("a");
datas1.add("b");
List<String> datas2 = new ArrayList<String>();
datas2.add("c");
datas2.add("d");
datas2.add("e");
vars.put("datas1", datas1);
vars.put("datas2", datas2);
// 启动流程
ProcessInstance pi = runtimeService.startProcessInstanceByKey("process1", vars);
// 循环完成第一个任务的全部实例
List<Task> tasks = taskService.createTaskQuery().processInstanceId(pi.getId()).list();
System.out.println("第一个任务的实例数量:" + tasks.size());
// 完成全部任务
for (Task task : tasks) {
taskService.complete(task.getId());
}
tasks = taskService.createTaskQuery().processInstanceId(pi.getId()).list();
System.out.println("第二个任务的实例数量:" + tasks.size());
第一个任务的实例数量:2
第二个任务的实例数量:3
3.3、获取循环元素
可以对集合进行遍历,在循环体中,如果需要使用集合中的元素,则可以为每一个元素定义一个别名,然后可以在相应的子执行流的范围中使用该参数。为集合中的元素定义别名,可以使用BPMN的方式或者Activiti的扩展方式。使用BPMN方式,可以为
multiInstanceLoopCharacteristics
增加inputDataltem
子元素来实现,使用Activiti的扩展方式,可以为multiInstanceLoopCharacteristics增加activiti:elementVariable
属性来实现。
<process id="process1" name="process1">
<startEvent id="startevent1" name="Start"></startEvent>
<serviceTask id="servicetask1" name="Service Task"
activiti:class="pers.zhang.service.ServiceA">
<multiInstanceLoopCharacteristics
isSequential="true">
<loopDataInputRef>datas1</loopDataInputRef>
<inputDataItem name="data" />
</multiInstanceLoopCharacteristics>
</serviceTask>
<serviceTask id="servicetask2" name="Service Task"
activiti:class="pers.zhang.service.ServiceB">
<multiInstanceLoopCharacteristics
isSequential="true" activiti:collection="${datas2}"
activiti:elementVariable="data">
</multiInstanceLoopCharacteristics>
</serviceTask>
<endEvent id="endevent1" name="End"></endEvent>
<sequenceFlow id="flow1" name="" sourceRef="startevent1"
targetRef="servicetask1"></sequenceFlow>
<sequenceFlow id="flow2" name="" sourceRef="servicetask1"
targetRef="servicetask2"></sequenceFlow>
<userTask id="usertask1" name="End Task"></userTask>
<sequenceFlow id="flow3" name="" sourceRef="servicetask2"
targetRef="usertask1"></sequenceFlow>
<sequenceFlow id="flow4" name="" sourceRef="usertask1"
targetRef="endevent1"></sequenceFlow>
</process>
public class ServiceA implements JavaDelegate {
public void execute(DelegateExecution execution) {
System.out.println("第一个任务,当前执行流id:" + execution.getId() + ", 父执行流id: "
+ execution.getParentId());
System.out.println("获取循环参数:" + execution.getVariable("data"));
}
}
public class ServiceB implements JavaDelegate {
public void execute(DelegateExecution execution) {
System.out.println("第二个任务,当前执行流id:" + execution.getId() + ", 父执行流id: "
+ execution.getParentId());
System.out.println("获取循环参数:" + execution.getVariable("data"));
}
}
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
RepositoryService repositoryService = engine.getRepositoryService();
RuntimeService runtimeService = engine.getRuntimeService();
repositoryService.createDeployment().addClasspathResource("demo13/ElementInstance.bpmn").deploy();
Map<String, Object> vars = new HashMap<String, Object>();
// 设置参数
List<String> datas1 = new ArrayList<String>();
datas1.add("a");
datas1.add("b");
vars.put("datas1", datas1);
List<String> datas2 = new ArrayList<String>();
datas2.add("c");
datas2.add("d");
vars.put("datas2", datas2);
// 启动流程
ProcessInstance pi = runtimeService.startProcessInstanceByKey("process1", vars);
// 查询执行流
Execution exe = runtimeService.createExecutionQuery()
.processInstanceId(pi.getId()).onlyChildExecutions()
.singleResult();
System.out.println("主执行流id:" + exe.getId())
第一个任务,当前执行流id:14, 父执行流id: 12
获取循环参数:a
第一个任务,当前执行流id:14, 父执行流id: 12
获取循环参数:b
第二个任务,当前执行流id:22, 父执行流id: 12
获取循环参数:c
第二个任务,当前执行流id:22, 父执行流id: 12
获取循环参数:d
主执行流id:12
3.4、循环的内置参数
在对多实例活动进行循环的过程中,可以使用一些内置的参数,例如实例的总数、已完成实例数、当前循环索引等。这些参数的名称以及描述如下。
- nrOfInstances:实例总数,使用loopCardinality元素的话,由该元素值决定,使用外部集合的话,由集合大小决定。
- nrOfActiveInstances:当前正在执行的实例数,如果是按顺序执行的多实例活动,该值总是为1,如果是同时进行的多实例活动,则每执行完一个实例,该值将会减少1。
- nrOfCompletedInstances:已经完成的实例数。
- loopCounter:当前循环的索引。
当执行流到达多实例的活动时,对于按顺序执行的实例,会创建新的执行流用于执行这些活动的实例,因此以上4个参数,均可以在该执行流的范围中获取;对于同时执行的实例,除了会创建一条新的执行流外,还会为每个实例创建一条执行流。以上的loopCounter参数,只能在对应实例的执行流范围中使用。
<process id="process1" name="process1">
<startEvent id="startevent1" name="Start"></startEvent>
<serviceTask id="servicetask2" name="Service Task"
activiti:class="pers.zhang.service.InternalVariableServiceA">
<multiInstanceLoopCharacteristics
isSequential="true">
<loopCardinality>3</loopCardinality>
</multiInstanceLoopCharacteristics>
</serviceTask>
<endEvent id="endevent1" name="End"></endEvent>
<serviceTask id="servicetask3" name="Service Task"
activiti:class="pers.zhang.service.InternalVariableServiceB">
<multiInstanceLoopCharacteristics
isSequential="false">
<loopCardinality>3</loopCardinality>
</multiInstanceLoopCharacteristics>
</serviceTask>
<userTask id="usertask1" name="End Task"></userTask>
<sequenceFlow id="flow2" name="" sourceRef="servicetask2"
targetRef="servicetask3"></sequenceFlow>
<sequenceFlow id="flow3" name="" sourceRef="servicetask3"
targetRef="usertask1"></sequenceFlow>
<sequenceFlow id="flow4" name="" sourceRef="usertask1"
targetRef="endevent1"></sequenceFlow>
<sequenceFlow id="flow5" name="" sourceRef="startevent1"
targetRef="servicetask2"></sequenceFlow>
</process>
public class InternalVariableServiceA implements JavaDelegate {
public void execute(DelegateExecution execution) {
System.out.println("按顺序执行的多实例活动,执行流id:" + execution.getId());
System.out.println("实例总数: " + execution.getVariable("nrOfInstances")
+ ", 当前执行的任务数: " + execution.getVariable("nrOfActiveInstances")
+ ", 已完成的任务数: "
+ execution.getVariable("nrOfCompletedInstances") + ", 当前索引: "
+ execution.getVariable("loopCounter"));
}
}
public class InternalVariableServiceB implements JavaDelegate {
public void execute(DelegateExecution execution) {
System.out.println("同时执行的多实例活动,执行流id:" + execution.getId());
System.out.println("实例总数: " + execution.getVariable("nrOfInstances")
+ ", 当前执行的任务数: " + execution.getVariable("nrOfActiveInstances")
+ ", 已完成的任务数: "
+ execution.getVariable("nrOfCompletedInstances") + ", 当前索引: "
+ execution.getVariable("loopCounter"));
}
}
// 创建流程引擎
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
// 得到流程存储服务组件
RepositoryService repositoryService = engine.getRepositoryService();
// 得到运行时服务组件
RuntimeService runtimeService = engine.getRuntimeService();
// 部署流程文件
repositoryService.createDeployment()
.addClasspathResource("demo13/InternalVariable.bpmn")
.deploy();
// 启动流程
runtimeService.startProcessInstanceByKey("process1");
按顺序执行的多实例活动,执行流id:8
实例总数: 3, 当前执行的任务数: 1, 已完成的任务数: 0, 当前索引: 0
按顺序执行的多实例活动,执行流id:8
实例总数: 3, 当前执行的任务数: 1, 已完成的任务数: 1, 当前索引: 1
按顺序执行的多实例活动,执行流id:8
实例总数: 3, 当前执行的任务数: 1, 已完成的任务数: 2, 当前索引: 2
同时执行的多实例活动,执行流id:16
实例总数: 3, 当前执行的任务数: 3, 已完成的任务数: 0, 当前索引: 0
同时执行的多实例活动,执行流id:17
实例总数: 3, 当前执行的任务数: 2, 已完成的任务数: 1, 当前索引: 1
同时执行的多实例活动,执行流id:18
实例总数: 3, 当前执行的任务数: 1, 已完成的任务数: 2, 当前索引: 2
3.5、循环结束条件
多实例活动的各个实例在执行时,如果需要在达到某种条件时跳出循环,则可以为multiInstanceLoopCharacteristics
添加completionCondition
子元素来实现。循环结束条件的设定方法,可以应用在诸如投票的业务上,例如赞成票超过50%后跳出循环,并且让流程继续向前进行,此时可以使用completionCondition元素。
<process id="process1" name="process1" isExecutable="true">
<startEvent id="startevent1" name="Start"></startEvent>
<serviceTask id="servicetask1" name="Service Task"
activiti:class="pers.zhang.delegate.CompleteDelegate">
<multiInstanceLoopCharacteristics
isSequential="true" activiti:collection="${datas}"
activiti:elementVariable="data">
<completionCondition>${nrOfCompletedInstances >= 2}</completionCondition>
</multiInstanceLoopCharacteristics>
</serviceTask>
<userTask id="usertask1" name="End Task"></userTask>
<endEvent id="endevent1" name="End"></endEvent>
<sequenceFlow id="flow1" sourceRef="startevent1"
targetRef="servicetask1"></sequenceFlow>
<sequenceFlow id="flow2" sourceRef="servicetask1"
targetRef="usertask1"></sequenceFlow>
<sequenceFlow id="flow3" sourceRef="usertask1" targetRef="endevent1"></sequenceFlow>
</process>
public class CompleteDelegate implements JavaDelegate {
public void execute(DelegateExecution execution) {
System.out.println(execution.getVariable("loopCounter")
+ "执行JavaDelegate类, 数据项为:" + execution.getVariable("data"));
}
}
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
RepositoryService repositoryService = engine.getRepositoryService();
RuntimeService runtimeService = engine.getRuntimeService();
repositoryService.createDeployment().addClasspathResource("demo13/CompleteCondition.bpmn").deploy();
// 初始化参数
Map<String, Object> vars = new HashMap<String, Object>();
List<String> datas = new ArrayList<String>();
datas.add("a");
datas.add("b");
datas.add("c");
datas.add("d");
vars.put("datas", datas);
// 启动流程
runtimeService.startProcessInstanceByKey("process1", vars);
0执行JavaDelegate类, 数据项为:a
1执行JavaDelegate类, 数据项为:b
3.6、补偿处理者
补偿处理者是指当补偿事件被触发时,来处理补偿行为的流程活动。补偿处理者不能包含普通的顺序流,它仅仅用于执行补偿,补偿处理者不能有输入或者输出的顺序流,它必须与某个补偿事件相关联。
图中的“cancel hotel reservation”任务为补偿处理者。要定义一个活动是补偿处理者,可以为活动添加isForCompensation-true来配置,例如,可以如下配置一个补偿处理者:
<serviceTask id="undoBookHotel"isForCompensation="true"
activiti:class="
</serviceTask>
在以上配置中,定义了一个Service Task作为补偿处理者,除了Service Task外,还可以使用其他的流程活动作为补偿处理者。