中间事件
中间事件是指可以单独作为流程元素的事件,BPMN2.0规范中所指的中间事件也包括边界事件。中间事件作为流程元素表示对事件的捕获与事件的触发,一类中间事件可以在流程中等待被触发,一类中间事件会在流程中自动被触发并抛出结果(触发信息)。
中间事件分类
中间事件按照其特性可以分为两类:中间Catching(捕获)事件和中间Throwing(抛出)事件,当流程到达中间Catching事件时,它会一直在等待被触发,直接接收到的信息,才会被触发,而当流程到达中间Throwing事件时,该事件会自动被触发并抛出相应的结果或者信息。
BPMN2.0中定义的中间Catching事件有:消息(Message)中间事件、定时器(Timer)中间事件、条件(Conditional)中间事件、连接(Link)中间事件、信号(Signal)中间事件、组合(Multiple)中间事件和并行(Parallel Multiple)中间事件。
BPMN2.0中定义的中间Throwing事件有:无指定(None)中间事件、消息(Message)中间事件、升级(Escalation)中间事件、补偿(Compensation)中间事件、连接(Link)中间事件、信号(Signal)中间事件和组合(Multiple)中间事件。
之前写了一篇定时任务的事件,可以参考: https://blog.csdn.net/qq_33333654/article/details/101364313
信号中间Catching事件
信号中间事件分为Catching事件和Throwing事件,一个信号中间Catching事件会等待被触发,直接到该事件接收到相应的信号,与其他事件不同的是,当信号事件接收到信号后,该信号不会被消耗掉,如果存在多个引用了相同信号的事件,那么当接收到信号时,这些事件被一并被触发,即使它们不在同一个流程实例中。假设现有一个系统处理用户购买商品的流程,用户在选择商品后,出现并行分支,用户需要进行支付,而系统要产生订单并等待用户支付完成,此时可以使用信号中间Catching事件,流程如图11-14所示。

如图11-14所示,在用户选择完商品后,流程出现并行分支,一个分支会进用户支付,另外一个会由系统生成订单,系统生成完订单后,会到达信号中间Catching事件,此时该事件会一直等待信号,当用户支付的UserTask完成后,可以使用RuntimeService的signaleEventReceived方法发送信号。对应图11-14的流程文件内容如代码清单11-33所示。
代码清单11-33:
<signal id="finishPay" name="finishPay"></signal>
<process id="scProcess" name="scProcess">
<startEvent id="startevent1" name="Start"></startEvent>
<userTask id="usertask1" name="选择商品"></userTask>
<parallelGateway id="parallelgateway1" name="Parallel Gateway"></parallelGateway>
<userTask id="usertask2" name="用户支付"></userTask>
<serviceTask id="servicetask1" name="系统生成订单"
activiti:class="org.crazyit.activiti.GenOrderDelegate"></serviceTask>
<intermediateCatchEvent id="signalintermediatecatchevent1"
name="SignalCatchEvent">
<signalEventDefinition signalRef="finishPay"></signalEventDefinition>
</intermediateCatchEvent>
<parallelGateway id="parallelgateway2" name="Parallel Gateway"></parallelGateway>
<endEvent id="endevent1" name="End"></endEvent>
<sequenceFlow id="flow1" name="" sourceRef="startevent1"
targetRef="usertask1"></sequenceFlow>
<sequenceFlow id="flow2" name="" sourceRef="usertask1"
targetRef="parallelgateway1"></sequenceFlow>
<sequenceFlow id="flow3" name="" sourceRef="parallelgateway1"
targetRef="usertask2"></sequenceFlow>
<sequenceFlow id="flow5" name="" sourceRef="parallelgateway2"
targetRef="endevent1"></sequenceFlow>
<sequenceFlow id="flow6" name="" sourceRef="parallelgateway1"
targetRef="servicetask1"></sequenceFlow>
<sequenceFlow id="flow7" name="" sourceRef="servicetask1"
targetRef="signalintermediatecatchevent1"></sequenceFlow>
<userTask id="usertask3" name="系统完成订单"></userTask>
<sequenceFlow id="flow8" name="" sourceRef="usertask2"
targetRef="parallelgateway2"></sequenceFlow>
<sequenceFlow id="flow9" name="" sourceRef="usertask3"
targetRef="parallelgateway2"></sequenceFlow>
<sequenceFlow id="flow10" name=""
sourceRef="signalintermediatecatchevent1" targetRef="usertask3"></sequenceFlow>
</process>
代码清单11-33定义了一个信号中间Catching事件,该事件引用了“finishPay”的信号,代码清单11-33中的ServiceTask,对应的类为GenOrderDelegate,该类的execute方法仅仅只输出“系统完成生成订单”。代码清单11-34加载该流程文件并运行流程。
代码清单11-34:
// 创建流程引擎
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
// 得到流程存储服务组件
RepositoryService repositoryService = engine.getRepositoryService();
// 得到运行时服务组件
RuntimeService runtimeService = engine.getRuntimeService();
TaskService taskService = engine.getTaskService();
// 部署流程文件
repositoryService.createDeployment()
.addClasspathResource("bpmn/SignalCatchingEvent.bpmn").deploy();
// 启动流程
runtimeService.startProcessInstanceByKey("scProcess");
Task firstTask = taskService.createTaskQuery().singleResult();
taskService.complete(firstTask.getId()); ①
// 此时会出现并行的两个流程分支,查找用户任务并完成
Task payTask = taskService.createTaskQuery().singleResult();
// 完成任务
taskService.complete(payTask.getId()); ②
// 发送信号完成支付
runtimeService.signalEventReceived("finishPay");
Task finishTask = taskService.createTaskQuery().singleResult();
System.out.println("当前流程任务:" + finishTask.getName());
在代码清单11-34中,先将“选择商品”任务完成(代码①),当出现流程分支后,再完成“用户支付”的UserTask(代码②),然后使用RuntimeService的signaleEventReceived方法发送信号,此时到达信号中间Catching事件的流程分支会捕获到该信号,流程到达“系统完成订单”的UserTask,运行代码清单11-34,输出效果如下:
系统完成生成订单
当前流程任务:系统完成订单

信号中间Throwing事件
信号中间Throwing事件用于抛出信号,当流程到达该事件时,会直接抛出信号,其他引用了与其相同的信号Catching事件会被触发。
信号中间Throwing事件分为同步与异步两种,可以为信号事件定义元素(signalEventDefinition)设置activiti:async属性,该值设置为true时,表示这个是一个异步的信号中间Throwing事件,该属性默认值为false。如果该事件是一个同步的信号中间Throwing事件,那么在抛出信号时,捕获这个信号的信号Catching事件将会在同一个事务中完成各自的工作(流程向前进行),如果其有一个信号Catching事件出现异常,那么全部的信号事件将会失败。而异步的信号中间Throwing事件在抛出信号时,即使其中一个信号Catching事件失败,其他已经成功的信号号件也不会受到影响。修改11.6.3中的流程,新的流程如图11-15所示。

如图11-15所示,为用户支付的流程分支加入了信号中间Throwing事件,“用户支付”的UserTask完成后,就会马上触发该事件,这个事件会抛出信号,此时定义在另外两个分支的信号中间Catching事件会捕获该信号。该流程对应的流程文件内容如代码清单11-35所示。
代码清单11-35:
<signal id="finishPay" name="finishPay"></signal>
<process id="stProcess" name="stProcess">
<startEvent id="startevent1" name="Start"></startEvent>
<userTask id="usertask1" name="选择商品"></userTask>
<parallelGateway id="parallelgateway1" name="Parallel Gateway"></parallelGateway>
<userTask id="usertask2" name="用户支付"></userTask>
<serviceTask id="servicetask1" name="系统生成订单"
activiti:class="org.crazyit.activiti.GenOrderDelegate"></serviceTask>
<intermediateCatchEvent id="signalintermediatecatchevent1" ①
name="SignalCatchEvent">
<signalEventDefinition signalRef="finishPay"></signalEventDefinition>
</intermediateCatchEvent>
<parallelGateway id="parallelgateway2" name="Parallel Gateway"></parallelGateway>
<endEvent id="endevent1" name="End"></endEvent>
<userTask id="usertask3" name="系统完成订单"></userTask>
<intermediateThrowEvent id="signalintermediatethrowevent1" ②
name="SignalThrowEvent">
<signalEventDefinition signalRef="finishPay"
activiti:async="true"></signalEventDefinition>
</intermediateThrowEvent>
<intermediateCatchEvent id="signalintermediatecatchevent2" ③
name="SignalCatchEvent">
<signalEventDefinition signalRef="finishPay"></signalEventDefinition>
</intermediateCatchEvent>
...省略顺序流元素
</process>
代码清单11-35中的①和③分别定义了一个信号中间Catching事件,②定义了一个信号中间Throwing事件,该信号事件定义的元素加入了activiti:async属性,并且将这个属性值设置为true,此时该事件将成为一个异步的信号中间Throwing事件。代码清单11-36加载该流程文件并执行流程。
代码清单11-36:
// 创建流程引擎
ProcessEngine engine = ProcessEngines.getDefaultProcessEngine();
// 得到流程存储服务组件
RepositoryService repositoryService = engine.getRepositoryService();
// 得到运行时服务组件
RuntimeService runtimeService = engine.getRuntimeService();
// 得到任务服务组件
TaskService taskService = engine.getTaskService();
// 得到管理服务组件
ManagementService managementService = engine.getManagementService();
// 部署流程文件
repositoryService.createDeployment()
.addClasspathResource("bpmn/SignalThrowingEvent.bpmn").deploy();
// 启动流程
runtimeService.startProcessInstanceByKey("stProcess");
// 完成选择商品任务
Task firstTask = taskService.createTaskQuery().singleResult();
taskService.complete(firstTask.getId());
// 完成用户支付任务
Task payTask = taskService.createTaskQuery().singleResult();
taskService.complete(payTask.getId());
// 由于使用了异步的中间Throwing事件,因此会产生2条工作数据
List<Job> jobs = managementService.createJobQuery().list();
System.out.println(jobs.size());
代码清单11-36中先启动流程,由于本例中“用户支付”任务后有一个信号中间Throwing事件,因此在完成“用户支付”的任务后,不需要再使用RuntimeService的signalEventReceived方法发送信号,这个信号中间Throwing事件会自动触发并抛出信号,由于这个事件是异步的,因此接收这个信号的两个Catching事件,将会变为两条工作数据保存到一般工作表(ACT_RU_JOB)中,如果想看到这两条数据,可以在配置文件中关闭异步执行器。运行代码清单11-36,输出结果如下:
系统完成生成订单
2
消息中间事件
在BPMN2.0规范中,消息中间事件有Throwing和Catching两种,而Activiti当前只对Catching有实现,只提供了消息中间Catching事件。当流程到达一个消息中间事件时,该事件会等待消息来触发,可以使用RuntimeSerice的messageEventReceived方法来发送消息。与信号事件不同的是,消息事件需要向特定的执行流发送消息,而信号事件可以向全部(不同流程实例)的执行流发送信号。
在BPMN2.0规范中,消息表示流程参与者沟通的信息,因此在业务流程中出现需要使用这些沟通信息来驱动流程前进的元素,可以使用消息中间事件。使用以下配置片断定义一个消息中间事件:
<intermediateCatchEvent id="messageintermediatecatchevent1"
name="MessageCatchEvent">
<messageEventDefinition messageRef="myMsg"></messageEventDefinition>
</intermediateCatchEvent>
当执行流到达消息中间事件时,可以使用RuntimeService的messageEvnetReceived方法触发该消息事件,消息中间事件的使用在此不再赘述。
无指定中间事件
无指定(None)中间事件是一个Throwing事件,在intermediateThrowEvent元素下不加入任何的事件定义元素,就构成一个无指定中间事件。即使无指定中间事件没有指定任何的事件定义,看起来没有任何的作用,但是可以为其提供流程监听器,来表示流程状态的改变,关于流程监听器的使用将会后续学习中慢慢完善。