排他网关(也叫异或(XOR)网关,或叫基于数据的排他网关),用来在流程中实现决策。 当流程执行到这个网关,所有分支都会判断条件是否为true,如果为true则执行该分支,
注意,排他网关只会选择一个为true的分支执行。(即使有两个分支条件都为true,排他网关也会只选择一条分支去执行)
为什么要用排他网关?
不用排他网关也可以实现分支,如下图:
上图中,在连线的condition条件上设置分支条件。
缺点:如果条件都不满足(比如num的值是4的时候),不使用排他网关,流程就结束了(是异常结束)。
如果 使用排他网关决定分支的走向,如下:
如果从网关出去的线所有条件都不满足则系统抛出异常。
org.activiti.engine.ActivitiException: No outgoing sequence flow of the exclusive gateway 'exclusivegateway1' could be selected for continuing the process
at org.activiti.engine.impl.bpmn.behavior.ExclusiveGatewayActivityBehavior.leave(ExclusiveGatewayActivityBehavior.java:85)
说明 :经过排他网关必须要有一条且只有一条分支走。
/**
* 测试排他网关
*/
public class ExclusiveGetwayTest {
//部署排他网关的流程定义
public static void main(String[] args) {
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
RepositoryService repositoryService = processEngine.getRepositoryService();
Deployment deployment = repositoryService.createDeployment().addClasspathResource("bpmn/holiday5.bpmn")
.name("排他网关请假流程").deploy();
System.out.println("流程定义的ID:"+deployment.getId());
}
}
//启动流程实例,设置流程变量
public static void main(String[] args) {
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
RuntimeService runtimeService = processEngine.getRuntimeService();
//设置流程变量
Map map = new HashMap<>();
map.put("num",4);
//启动流程实例
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("myholiday5", "businessKey:1001",map);
System.out.println("流程定义的id:"+processInstance.getProcessDefinitionId());
}
可以看到,我设置的num的值是4,特意为了异常而设置的。
我们先执行第一个任务,然后再次执行部门经理的任务,就会发现报错了:
//完成任务,执行流程
public static void main(String[] args) {
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
TaskService taskService = processEngine.getTaskService();
TaskQuery taskQuery = taskService.createTaskQuery();
Task task = taskQuery.processDefinitionKey("myholiday5").taskAssignee("lisi").singleResult();
if(task!=null){
taskService.complete(task.getId());
System.out.println("完成任务");
}
}
执行发现,报错如下:
Exception in thread "main" org.activiti.engine.ActivitiException: No outgoing sequence flow of the exclusive gateway '_5' could be selected for continuing the process
at org.activiti.engine.impl.bpmn.behavior.ExclusiveGatewayActivityBehavior.leave(ExclusiveGatewayActivityBehavior.java:105)
at org.activiti.engine.impl.bpmn.behavior.FlowNodeActivityBehavior.execute(FlowNodeActivityBehavior.java:38)
at org.activiti.engine.impl.agenda.ContinueProcessOperation.executeActivityBehavior(ContinueProcessOperation.java:210)
at org.activiti.engine.impl.agenda.ContinueProcessOperation.executeSynchronous(ContinueProcessOperation.java:146)
at org.activiti.engine.impl.agenda.ContinueProcessOperation.continueThroughFlowNode(ContinueProcessOperation.java:101)
at org.activiti.engine.impl.agenda.ContinueProcessOperation.run(ContinueProcessOperation.java:66)
at org.activiti.engine.impl.interceptor.CommandInvoker.executeOperation(CommandInvoker.java:73)
at org.activiti.engine.impl.interceptor.CommandInvoker.executeOperations(CommandInvoker.java:57)
at org.activiti.engine.impl.interceptor.CommandInvoker.execute(CommandInvoker.java:42)
at org.activiti.engine.impl.interceptor.TransactionContextInterceptor.execute(TransactionContextInterceptor.java:48)
at org.activiti.engine.impl.interceptor.CommandContextInterceptor.execute(CommandContextInterceptor.java:63)
at org.activiti.engine.impl.interceptor.LogInterceptor.execute(LogInterceptor.java:35)
at org.activiti.engine.impl.cfg.CommandExecutorImpl.execute(CommandExecutorImpl.java:44)
at org.activiti.engine.impl.cfg.CommandExecutorImpl.execute(CommandExecutorImpl.java:39)
at org.activiti.engine.impl.TaskServiceImpl.complete(TaskServiceImpl.java:192)
at com.zdw.activiti04.ExclusiveGetwayTest.main(ExclusiveGetwayTest.java:24)
这就跟我们前面说的,当设置的值不满足网关的任一条线路的时候,会报错的。
//完成任务时设置流程变量的值
public static void main(String[] args) {
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
TaskService taskService = processEngine.getTaskService();
TaskQuery taskQuery = taskService.createTaskQuery();
Task task = taskQuery.processDefinitionKey("myholiday5").taskAssignee("lisi").singleResult();
//设置流程变量
Map map = new HashMap<>();
map.put("num",6);
if(task!=null){
taskService.setVariables(task.getId(),map);
taskService.complete(task.getId());
System.out.println("完成任务");
}
}
执行上面的程序的时候,就不会报错了,因为我们修改了流程变量num的值为6,那么排他网关就知道下一步该走哪条线了。执行完上面的代码之后,查询act_ru_task表,走的是总经理审核了。
接下来的业务操作就跟之前的是一样的了,不再赘述了。
并行网关允许将流程分成多条分支,也可以把多条分支汇聚到一起,并行网关的功能是基于进入和外出顺序流的:
fork分支:并行后的所有外出顺序流,为每个顺序流都创建一个并发分支。
join汇聚:所有到达并行网关,在此等待的进入分支, 直到所有进入顺序流的分支都到达以后, 流程就会通过汇聚网关。
注意,如果同一个并行网关有多个进入和多个外出顺序流, 它就同时具有分支和汇聚功能。 这时,网关会先汇聚所有进入的顺序流,然后再切分成多个并行分支。
与其他网关的主要区别是,并行网关不会解析条件。 即使顺序流中定义了条件,也会被忽略。
//部署排他网关的流程定义
public static void main(String[] args) {
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
RepositoryService repositoryService = processEngine.getRepositoryService();
Deployment deployment = repositoryService.createDeployment().addClasspathResource("bpmn/holiday6.bpmn")
.name("并行网关请假流程").deploy();
System.out.println("流程定义的ID:"+deployment.getId());
}
//启动流程实例,设置流程变量
public static void main(String[] args) {
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
RuntimeService runtimeService = processEngine.getRuntimeService();
//设置流程变量
Map map = new HashMap<>();
map.put("num",2);
//启动流程实例
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("myholiday6", "businessKey:1001",map);
System.out.println("流程定义的id:"+processInstance.getProcessDefinitionId());
}
我们这里的完成任务就不啰嗦了,之间把任务执行到并行网关那里再来看效果。因为我们设置的num的值是2,所以不会走到总经理审核,直接就是走人事经理存档的。当我们执行完人事经理zhaoliu的任务后:
//完成任务
public static void main(String[] args) {
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
TaskService taskService = processEngine.getTaskService();
TaskQuery taskQuery = taskService.createTaskQuery();
Task task = taskQuery.processDefinitionKey("myholiday6").taskAssignee("zhaoliu").singleResult();
if(task!=null){
taskService.complete(task.getId());
System.out.println("完成任务");
}
}
查询表:act_ru_task,发现代办任务里面有两条记录:
这就是并行网关的特性,并行网关的每条线路的任务都要去执行。
当我们分别执行xiaoli和xiaoyu的任务之后,整个流程才会结束的,没有先后顺序约束,随便哪个先执行都可以的。
总结:所有分支到达汇聚结点,并行网关执行完成。
包含网关可以看做是排他网关和并行网关的结合体。 和排他网关一样,你可以在外出顺序流上定义条件,包含网关会解析它们。 但是主要的区别是包含网关可以选择多于一条顺序流,这和并行网关一样。
包含网关的功能是基于进入和外出顺序流的:
分支:所有外出顺序流的条件都会被解析,结果为true的顺序流会以并行方式继续执行, 会为每个顺序流创建一个分支。
汇聚:所有并行分支到达包含网关,会进入等待状态, 直到每个包含流程token的进入顺序流的分支都到达。 这是与并行网关的最大不同。换句话说,包含网关只会等待被选中执行了的进入顺序流。 在汇聚之后,流程会穿过包含网关继续执行。
企业体检流程,公司全体员工进行常规项检查、抽血化验,公司管理层除常规检查和抽血化验还要进行增加项检查。
只有userType参数的值等于2的员工,才参加附加项检查。
//部署排他网关的流程定义
public static void main(String[] args) {
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
RepositoryService repositoryService = processEngine.getRepositoryService();
Deployment deployment = repositoryService.createDeployment().addClasspathResource("bpmn/holiday7.bpmn")
.name("包含网关请假流程").deploy();
System.out.println("流程定义的ID:"+deployment.getId());
}
1、启动流程实例的时候,没有设置userType流程变量的值:
此时启动流程定义是可以成功的,但是当我们执行完成任务的操作的时候,会报错:
Exception in thread "main" org.activiti.engine.ActivitiException: Unknown property used in expression: ${userType==1 || userType==2}
at org.activiti.engine.impl.el.JuelExpression.getValue(JuelExpression.java:52)
at org.activiti.engine.impl.el.UelExpressionCondition.evaluate(UelExpressionCondition.java:36)
at org.activiti.engine.impl.util.condition.ConditionUtil.hasTrueCondition(ConditionUtil.java:34)
at org.activiti.engine.impl.agenda.TakeOutgoingSequenceFlowsOperation.leaveFlowNode(TakeOutgoingSequenceFlowsOperation.java:141)
at org.activiti.engine.impl.agenda.TakeOutgoingSequenceFlowsOperation.handleFlowNode(TakeOutgoingSequenceFlowsOperation.java:87)
at org.activiti.engine.impl.agenda.TakeOutgoingSequenceFlowsOperation.run(TakeOutgoingSequenceFlowsOperation.java:75)
at org.activiti.engine.impl.interceptor.CommandInvoker.executeOperation(CommandInvoker.java:73)
at org.activiti.engine.impl.interceptor.CommandInvoker.executeOperations(CommandInvoker.java:57)
at org.activiti.engine.impl.interceptor.CommandInvoker.execute(CommandInvoker.java:42)
at org.activiti.engine.impl.interceptor.TransactionContextInterceptor.execute(TransactionContextInterceptor.java:48)
at org.activiti.engine.impl.interceptor.CommandContextInterceptor.execute(CommandContextInterceptor.java:63)
at org.activiti.engine.impl.interceptor.LogInterceptor.execute(LogInterceptor.java:35)
at org.activiti.engine.impl.cfg.CommandExecutorImpl.execute(CommandExecutorImpl.java:44)
at org.activiti.engine.impl.cfg.CommandExecutorImpl.execute(CommandExecutorImpl.java:39)
at org.activiti.engine.impl.TaskServiceImpl.complete(TaskServiceImpl.java:192)
at com.zdw.activiti04.InclusiveGetewayTest.main(InclusiveGetewayTest.java:24)
Caused by: javax.el.PropertyNotFoundException: Cannot resolve identifier 'userType'
2、启动流程实例的时候,设置userType流程变量的值
//启动流程实例,设置流程变量
public static void main2(String[] args) {
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
RuntimeService runtimeService = processEngine.getRuntimeService();
//设置流程变量
Map map = new HashMap<>();
map.put("userType",2);
//启动流程实例
ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("myholiday7", "businessKey:1001",map);
System.out.println("流程定义的id:"+processInstance.getProcessDefinitionId());
}
1、首先完成的领取体检的任务
//完成领取体检单的任务
public static void main(String[] args) {
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
TaskService taskService = processEngine.getTaskService();
TaskQuery taskQuery = taskService.createTaskQuery();
Task task = taskQuery.processDefinitionKey("myholiday7").taskAssignee("zhangsan").singleResult();
if(task!=null){
taskService.complete(task.getId());
System.out.println("完成任务");
}
}
执行完上面的任务之后,我们查看表act_ru_task,可以看到有三个代办任务,因为我们设置的userType的值是2,所以三条线路都会执行的。
2、完成常规检查的任务
因为我设置的完成任务的assignee都是zhangsan,所以根据zhangsan查询的时候,就会查询出三个任务,这样可以一次性就把三个任务都执行完。但是为了看到包含网关的效果:只有所有的线路都执行完成之后才会结束。所以我们选择一个一个的去完成任务,因此查询的时候就不能仅仅通过执行人去查询了,还要加上任务名称name,具体如下:
//根据任务名称和执行人查询任务,并完成
public static void main(String[] args) {
ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
TaskService taskService = processEngine.getTaskService();
TaskQuery taskQuery = taskService.createTaskQuery();
Task task = taskQuery.processDefinitionKey("myholiday7").taskName("常规检查").taskAssignee("zhangsan").singleResult();
if(task!=null){
taskService.complete(task.getId());
System.out.println("完成任务");
}
}
执行之后,act_ru_task表中的常规检查的任务就会删除掉了。
我们可以只要修改任务名称,就可以按照上面的方式完成其他的任务(附加项检查,抽象化验,早餐)了。
先走到汇聚结点的分支,要等待其它分支走到汇聚。
等所有分支走到汇聚,包含网关就执行完成。
包含网关执行完成,分支和汇聚就从act_ru_execution删除。
小结:在分支时,需要判断条件,符合条件的分支,将会执行,符合条件的分支最终才进行汇聚。