java每日精进 7.26【流程设计5.0(中间事件+结束事件)】
1.中间事件核心概念
定位:位于开始事件与结束事件之间,影响流程路由但不启动/终止流程。
两类中间事件:
捕获事件(Catch):等待外部触发(如消息、信号到达)。
抛出事件(Throw):主动触发事件(如发送信号、抛出异常)。
与边界事件区别:边界事件附着在活动节点上,而中间事件独立存在于连线上。
1.1 中间抛出事件
1. 空中间抛出事件
作用:流程状态标记点,通过监听器执行自定义逻辑。
XML示例:
<process id="noneEventProcess"><intermediateThrowEvent id="statusMarker"><extensionElements><flowable:executionListener event="start"class="com.example.ProcessStatusListener"/> <!-- 监听状态变更 --></extensionElements></intermediateThrowEvent> </process>
使用场景:记录流程进入关键节点(如"订单已审核"),触发数据库日志记录。
定义创建了一个简单流程,其中包含一个无类型中间抛出事件,当流程执行到这个事件时,会触发指定的 Java 类(
MyExecutionListener
)中的逻辑。这种模式通常用于在流程执行过程中插入自定义业务逻辑、发送通知或与外部系统交互等场景。
2. 信号中间抛出事件
在流程执行过程中发送特定消息的机制
实例说明
具体实例
假设我们有一个订单处理流程,当订单确认后需要向支付系统发送支付请求,这就是消息中间抛出事件的典型应用场景。
BPMN XML 完整定义:
<?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"xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL http://www.omg.org/spec/BPMN/2.0/20100524/BPMN20.xsd"id="Definitions_1"targetNamespace="http://camunda.org/examples"><!-- 定义消息 --><message id="paymentRequestMsg" name="订单支付请求" /><message id="paymentResponseMsg" name="支付结果响应" /><!-- 订单流程 - 包含消息中间抛出事件 --><process id="orderProcess" name="订单处理流程" isExecutable="true"><startEvent id="start" /><sequenceFlow id="flow1" sourceRef="start" targetRef="confirmOrder" /><userTask id="confirmOrder" name="确认订单" /><sequenceFlow id="flow2" sourceRef="confirmOrder" targetRef="sendPaymentRequest" /><!-- 消息中间抛出事件:发送支付请求 --><intermediateThrowEvent id="sendPaymentRequest"><messageEventDefinition messageRef="paymentRequestMsg" /></intermediateThrowEvent><sequenceFlow id="flow3" sourceRef="sendPaymentRequest" targetRef="waitPaymentResponse" /><!-- 消息中间捕获事件:等待支付响应 --><intermediateCatchEvent id="waitPaymentResponse"><messageEventDefinition messageRef="paymentResponseMsg" /></intermediateCatchEvent><sequenceFlow id="flow4" sourceRef="waitPaymentResponse" targetRef="end" /><endEvent id="end" /></process><!-- 支付流程 - 接收支付请求 --><process id="paymentProcess" name="支付处理流程" isExecutable="true"><!-- 消息开始事件:接收支付请求 --><startEvent id="paymentStart"><messageEventDefinition messageRef="paymentRequestMsg" /></startEvent><sequenceFlow id="flow5" sourceRef="paymentStart" targetRef="processPayment" /><serviceTask id="processPayment" name="处理支付" /><sequenceFlow id="flow6" sourceRef="processPayment" targetRef="sendPaymentResponse" /><!-- 消息中间抛出事件:发送支付响应 --><intermediateThrowEvent id="sendPaymentResponse"><messageEventDefinition messageRef="paymentResponseMsg" /></intermediateThrowEvent><sequenceFlow id="flow7" sourceRef="sendPaymentResponse" targetRef="paymentEnd" /><endEvent id="paymentEnd" /></process>
</definitions>
// 1. 部署流程定义
RepositoryService repositoryService = processEngine.getRepositoryService();
repositoryService.createDeployment().addClasspathResource("order-payment-process.bpmn20.xml").deploy();// 2. 启动订单流程
RuntimeService runtimeService = processEngine.getRuntimeService();
ProcessInstance orderProcessInstance = runtimeService.startProcessInstanceByKey("orderProcess");
System.out.println("订单流程已启动,ID: " + orderProcessInstance.getId());// 3. 完成"确认订单"任务
TaskService taskService = processEngine.getTaskService();
Task confirmTask = taskService.createTaskQuery().processInstanceId(orderProcessInstance.getId()).singleResult();
taskService.complete(confirmTask.getId());// 此时流程会执行到sendPaymentRequest节点,发送支付请求消息
// 支付流程会自动启动并处理支付(因为有消息开始事件)// 4. 模拟支付完成后,发送支付响应消息
// 查找等待支付响应的执行实例
Execution execution = runtimeService.createExecutionQuery().processInstanceId(orderProcessInstance.getId()).messageEventSubscriptionName("paymentResponseMsg").singleResult();// 发送响应消息
runtimeService.messageEventReceived("paymentResponseMsg", execution.getId());
System.out.println("支付响应已发送,订单流程将继续执行");
作用:全局广播信号,触发所有匹配的信号捕获事件(包括开始/边界/中间事件)。
XML定义:
<signal id="alertSignal" name="紧急告警" /> <!-- 信号定义 --> <process id="signalProcess"><intermediateThrowEvent id="throwAlert"><signalEventDefinition signalRef="alertSignal" /> <!-- 抛出信号 --></intermediateThrowEvent> </process>
触发效果:
并行流程中订阅了
alertSignal
的信号边界事件会中断当前任务。信号开始事件启动新流程实例。
消息中间抛出事件
- 是 BPMN 中的一种事件类型,用于在流程执行到特定节点时主动发送消息
- 实现的是点对点通信模式,需要有明确的消息接收方
- 通常用于不同流程之间或流程与外部系统之间的通信
XML 定义解析
<message>
标签:定义消息元数据,id
是唯一标识,name
是可读名称<intermediateThrowEvent>
标签:定义中间抛出事件<messageEventDefinition>
标签:将事件与特定消息关联,messageRef
引用消息的id
接收机制
- 必须有对应的消息捕获事件(如消息中间捕获事件或消息边界事件)来接收此消息
- 形成了 "发送 - 接收" 的消息传递模式
- 整个流程包含两个子流程:订单处理流程和支付处理流程
- 订单流程在确认订单后,通过 "消息中间抛出事件" 发送支付请求
- 支付流程通过 "消息开始事件" 接收支付请求并处理
- 支付完成后,支付流程通过另一个 "消息中间抛出事件" 发送支付响应
- 订单流程通过 "消息中间捕获事件" 等待并接收支付响应,然后继续执行后续流程
实例说明
- 整个流程包含两个子流程:订单处理流程和支付处理流程
- 订单流程在确认订单后,通过 "消息中间抛出事件" 发送支付请求
- 支付流程通过 "消息开始事件" 接收支付请求并处理
- 支付完成后,支付流程通过另一个 "消息中间抛出事件" 发送支付响应
- 订单流程通过 "消息中间捕获事件" 等待并接收支付响应,然后继续执行后续流程
3. 消息中间抛出事件
作用:向特定接收方发送消息(点对点通信)。
XML定义:
<message id="paymentMsg" name="支付请求" /> <!-- 消息定义 --> <process id="paymentProcess"><intermediateThrowEvent id="sendPayment"><messageEventDefinition messageRef="paymentMsg" /> <!-- 抛出消息 --></intermediateThrowEvent> </process>
接收方:必须存在对应的消息捕获事件(如消息中间捕获事件)。
实例:
// 外部系统通过API触发消息 runtimeService.messageEventReceived("paymentMsg", executionId);
流程在
sendPayment
节点暂停,直到外部系统调用此API。xml示例
<!-- 消息中间抛出事件:发送支付请求 --><intermediateThrowEvent id="sendPaymentRequest"><messageEventDefinition messageRef="paymentRequestMsg" /></intermediateThrowEvent><sequenceFlow id="flow3" sourceRef="sendPaymentRequest" targetRef="waitPaymentResponse" /><!-- 消息中间捕获事件:等待支付响应 --><intermediateCatchEvent id="waitPaymentResponse"><messageEventDefinition messageRef="paymentResponseMsg" /></intermediateCatchEvent>
4. 补偿中间抛出事件
作用:用于在流程出现异常或需要取消操作时,触发已完成活动的补偿逻辑,恢复到之前的状态。
XML定义:
<intermediateThrowEvent id="undoActions"><compensateEventDefinition /> <!-- 触发补偿 --> </intermediateThrowEvent>
XML 定义解析
<intermediateThrowEvent id="undoActions">
:定义补偿中间抛出事件<compensateEventDefinition />
:标识这是一个补偿事件,用于触发补偿逻辑
补偿规则:
- 触发补偿:当流程执行到补偿中间抛出事件时,会触发已完成活动的补偿处理器
- 事务回滚:用于撤销之前的操作,恢复系统状态
- 补偿处理器:为需要补偿的活动定义的撤销逻辑(如取消订单、退款等)
补偿规则详解
- 反向触发:按活动完成顺序的逆序执行补偿(最后完成的活动最先补偿)
- 作用域限制:仅补偿同一子流程层级中定义了补偿处理器的活动
- 多实例活动:需等待所有实例都完成后才会触发补偿
补偿处理器定义
- 通过边界事件
<boundaryEvent>
定义,需指定attachedToRef
关联到需要补偿的活动 - 包含
<compensateEventDefinition />
标识这是一个补偿处理器
- 通过边界事件
实例:
<?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:flowable="http://flowable.org/bpmn"xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL http://www.omg.org/spec/BPMN/2.0/20100524/BPMN20.xsd http://flowable.org/bpmn http://flowable.org/bpmn/current/flowable-bpmn.xsd"id="Definitions_1"targetNamespace="http://flowable.org/examples"><process id="travelBookingProcess" name="旅行预订流程" isExecutable="true"><startEvent id="start" /><sequenceFlow id="flow1" sourceRef="start" targetRef="bookFlight" /><!-- 预订航班服务任务 --><serviceTask id="bookFlight" name="预订航班" flowable:class="com.example.BookFlightService"><extensionElements><flowable:executionListener event="end" class="com.example.LogFlightBookingListener" /></extensionElements></serviceTask><!-- 航班预订的补偿处理器 --><boundaryEvent id="compensateFlight" attachedToRef="bookFlight" cancelActivity="false"><compensateEventDefinition /></boundaryEvent><!-- 补偿航班预订的服务任务 --><serviceTask id="undoFlightBooking" name="取消航班预订" flowable:class="com.example.CancelFlightBookingService"><extensionElements><flowable:executionListener event="end" class="com.example.LogFlightCancellationListener" /></extensionElements></serviceTask><sequenceFlow id="flow2" sourceRef="compensateFlight" targetRef="undoFlightBooking" /><sequenceFlow id="flow3" sourceRef="undoFlightBooking" targetRef="compensationEnd" /><sequenceFlow id="flow4" sourceRef="bookFlight" targetRef="bookHotel" /><!-- 预订酒店服务任务 --><serviceTask id="bookHotel" name="预订酒店" flowable:class="com.example.BookHotelService"><extensionElements><flowable:executionListener event="end" class="com.example.LogHotelBookingListener" /></extensionElements></serviceTask><!-- 酒店预订的补偿处理器 --><boundaryEvent id="compensateHotel" attachedToRef="bookHotel" cancelActivity="false"><compensateEventDefinition /></boundaryEvent><!-- 补偿酒店预订的服务任务 --><serviceTask id="undoHotelBooking" name="取消酒店预订" flowable:class="com.example.CancelHotelBookingService"><extensionElements><flowable:executionListener event="end" class="com.example.LogHotelCancellationListener" /></extensionElements></serviceTask><sequenceFlow id="flow5" sourceRef="compensateHotel" targetRef="undoHotelBooking" /><sequenceFlow id="flow6" sourceRef="undoHotelBooking" targetRef="compensationEnd" /><sequenceFlow id="flow7" sourceRef="bookHotel" targetRef="paymentCheck" /><!-- 检查支付状态网关 --><exclusiveGateway id="paymentCheck" /><!-- 支付成功路径 --><sequenceFlow id="flow8" sourceRef="paymentCheck" targetRef="bookingSuccess"><conditionExpression xsi:type="tFormalExpression">${paymentSuccessful == true}</conditionExpression></sequenceFlow><!-- 支付失败路径 - 触发补偿 --><sequenceFlow id="flow9" sourceRef="paymentCheck" targetRef="undoActions"><conditionExpression xsi:type="tFormalExpression">${paymentSuccessful == false}</conditionExpression></sequenceFlow><!-- 补偿中间抛出事件 --><intermediateThrowEvent id="undoActions"><compensateEventDefinition /></intermediateThrowEvent><sequenceFlow id="flow10" sourceRef="undoActions" targetRef="compensationEnd" /><!-- 预订成功结束事件 --><endEvent id="bookingSuccess" /><!-- 补偿完成结束事件 --><endEvent id="compensationEnd" /></process> </definitions>
// 部署流程 repositoryService.createDeployment().addClasspathResource("travel-booking-process.bpmn20.xml").deploy();// 启动流程实例 ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("travelBookingProcess");// 模拟支付失败的情况 Task paymentTask = taskService.createTaskQuery().processInstanceId(processInstance.getId()).taskName("检查支付状态").singleResult();// 设置支付失败变量 Map<String, Object> variables = new HashMap<>(); variables.put("paymentSuccessful", false); taskService.complete(paymentTask.getId(), variables);// 此时流程会执行到undoActions节点,触发补偿 // 补偿顺序:先取消酒店预订(后完成的活动),再取消航班预订(先完成的活动)
实例执行流程说明
正常流程:
- 开始 → 预订航班 → 预订酒店 → 检查支付状态 → 支付成功 → 流程结束
补偿触发流程(当支付失败时):
- 开始 → 预订航班 → 预订酒店 → 检查支付状态 → 支付失败 → 触发补偿中间事件 (undoActions)
- 补偿执行顺序(反向执行):
- 触发酒店预订的补偿处理器 → 执行取消酒店预订
- 触发航班预订的补偿处理器 → 执行取消航班预订
- 补偿完成 → 流程结束
5. 升级中间抛出事件(Escalation)
作用:向父流程传递异常(非错误),不中断当前流程。
基本作用
- 跨层级通知:向父流程传递特定事件信息
- 非中断性:抛出事件后,当前流程(子流程)继续执行
- 异常传递:用于传递需要上级处理的特殊情况(非错误)
XML 定义解析
<intermediateThrowEvent id="escalateIssue">
:定义升级中间抛出事件<escalationEventDefinition escalationRef="managerApproval" />
:关联到特定升级定义,escalationRef
引用升级事件的 ID
工作机制
- 子流程中定义升级抛出事件
- 父流程中需定义对应的升级捕获事件(通常是边界事件)
- 事件触发后,父流程执行相应处理,子流程继续原有路径
与错误事件的关键区别
- 错误事件:抛出后会终止当前流程路径,通常用于处理异常情况
- 升级事件:仅向上传递信息,不影响当前流程执行,用于需要上级关注的业务情况
详细实例:采购审批流程
<?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:flowable="http://flowable.org/bpmn"xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL http://www.omg.org/spec/BPMN/2.0/20100524/BPMN20.xsd http://flowable.org/bpmn http://flowable.org/bpmn/current/flowable-bpmn.xsd"id="Definitions_1"targetNamespace="http://flowable.org/examples"><!-- 定义升级事件 --><escalation id="managerApproval" name="需要经理审批" /><!-- 父流程:采购管理流程 --><process id="purchaseManagementProcess" name="采购管理流程" isExecutable="true"><startEvent id="parentStart" /><sequenceFlow id="flow1" sourceRef="parentStart" targetRef="subProcess" /><!-- 子流程:采购审批流程 --><subProcess id="subProcess" name="采购审批流程"><startEvent id="subStart" /><sequenceFlow id="flow2" sourceRef="subStart" targetRef="checkAmount" /><!-- 检查采购金额 --><serviceTask id="checkAmount" name="检查采购金额" flowable:class="com.example.CheckPurchaseAmountService" /><sequenceFlow id="flow3" sourceRef="checkAmount" targetRef="amountGateway" /><!-- 金额判断网关 --><exclusiveGateway id="amountGateway" /><!-- 金额正常路径 --><sequenceFlow id="flow4" sourceRef="amountGateway" targetRef="departmentApproval"><conditionExpression xsi:type="tFormalExpression">${amount <= 10000}</conditionExpression></sequenceFlow><!-- 金额超限路径 - 触发升级 --><sequenceFlow id="flow5" sourceRef="amountGateway" targetRef="escalateIssue"><conditionExpression xsi:type="tFormalExpression">${amount > 10000}</conditionExpression></sequenceFlow><!-- 升级中间抛出事件 --><intermediateThrowEvent id="escalateIssue"><escalationEventDefinition escalationRef="managerApproval" /></intermediateThrowEvent><sequenceFlow id="flow6" sourceRef="escalateIssue" targetRef="departmentApproval" /><!-- 部门审批 --><userTask id="departmentApproval" name="部门审批" /><sequenceFlow id="flow7" sourceRef="departmentApproval" targetRef="subEnd" /><endEvent id="subEnd" /></subProcess><!-- 父流程中捕获升级事件的边界事件 --><boundaryEvent id="catchEscalation" attachedToRef="subProcess"><escalationEventDefinition escalationRef="managerApproval" /></boundaryEvent><sequenceFlow id="flow8" sourceRef="catchEscalation" targetRef="notifyManager" /><!-- 通知经理处理 --><serviceTask id="notifyManager" name="通知经理" flowable:class="com.example.NotifyManagerService" /><sequenceFlow id="flow9" sourceRef="notifyManager" targetRef="parentEnd" /><sequenceFlow id="flow10" sourceRef="subProcess" targetRef="parentEnd" /><endEvent id="parentEnd" /></process> </definitions>
Java 代码实现示例:
- 检查采购金额的服务类:
-
public class CheckPurchaseAmountService implements JavaDelegate {@Overridepublic void execute(DelegateExecution execution) {// 获取采购金额(实际应用中从流程变量获取)double amount = (double) execution.getVariable("purchaseAmount");System.out.println("检查采购金额: " + amount);// 设置金额变量用于网关判断execution.setVariable("amount", amount);} }
- 通知经理的服务类:
-
public class NotifyManagerService implements JavaDelegate {@Overridepublic void execute(DelegateExecution execution) {double amount = (double) execution.getVariable("amount");String purchaseId = (String) execution.getVariable("purchaseId");System.out.println("发送通知给经理: 采购单 " + purchaseId + " 金额为 " + amount + ",超过审批阈值需要关注");// 实际应用中这里会发送邮件、消息等} }
// 部署流程定义
repositoryService.createDeployment().addClasspathResource("purchase-approval-process.bpmn20.xml").deploy();// 启动流程实例,设置采购金额为15000(超过10000阈值)
Map<String, Object> variables = new HashMap<>();
variables.put("purchaseId", "PO-" + System.currentTimeMillis());
variables.put("purchaseAmount", 15000.00);ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("purchaseManagementProcess", variables);// 完成部门审批任务
Task departmentTask = taskService.createTaskQuery().processInstanceId(processInstance.getId()).taskName("部门审批").singleResult();taskService.complete(departmentTask.getId());
实例执行流程说明
正常执行路径:
- 父流程启动 → 进入子流程 → 检查采购金额(15000 元)
- 金额超过阈值(10000 元)→ 触发升级中间抛出事件 (escalateIssue)
- 子流程继续执行 → 部门审批 → 子流程结束 → 父流程结束
升级事件处理路径(并行发生):
- 升级事件触发 → 父流程的边界事件 (catchEscalation) 捕获事件
- 执行通知经理服务 → 完成经理通知流程
关键特性体现:
- 升级事件不中断子流程,子流程继续完成部门审批
- 父流程同时处理升级事件,发送通知给经理
- 实现了 "异常情况上报" 但 "业务继续处理" 的业务需求
注:父子事件联系如下:
<!-- 父流程中的子流程节点 -->
<subProcess id="subProcess" name="采购审批流程"><!-- 子流程内部逻辑(包含升级抛出事件) -->
</subProcess><!-- 绑定到子流程的升级边界事件 -->
<boundaryEvent id="catchEscalation" attachedToRef="subProcess"><!-- 关联到子流程抛出的升级事件ID --><escalationEventDefinition escalationRef="managerApproval" />
</boundaryEvent>
关键区别总结
事件类型 | 方向 | 触发方式 | 作用范围 |
---|---|---|---|
信号抛出 | 抛出 | 全局广播 | 跨流程实例 |
消息抛出 | 抛出 | 点对点发送 | 指定接收方 |
补偿抛出 | 抛出 | 触发补偿处理器 | 当前作用域 |
信号/消息捕获 | 捕获 | 等待外部触发 | 当前流程节点 |
计时器捕获 | 捕获 | 时间到达后自动触发 | 当前节点 |
最佳实践建议
空事件:结合监听器实现审计日志或指标统计。
信号事件:跨流程协同(如主流程通知子流程)。
消息事件:与外部系统集成(如等待支付回调)。
补偿事件:实现SAGA事务模式,确保数据一致性。
升级事件:构建分层异常处理机制(子流程→父流程)。
1.2 中间捕获事件
1. 计时器中间捕获事件
作用:延迟等待(如"3天后继续")。
<intermediateCatchEvent id="wait3Days"><timerEventDefinition><timeDuration>PT72H</timeDuration> <!-- 等待72小时 --></timerEventDefinition> </intermediateCatchEvent>
- 实例展示
<?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"xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL http://www.omg.org/spec/BPMN/2.0/20100524/BPMN20.xsd"id="Definitions_1"targetNamespace="http://example.org"><process id="leaveRequestProcess" name="假期申请流程" isExecutable="true"><startEvent id="start" /><sequenceFlow id="flow1" sourceRef="start" targetRef="submitRequest" /><!-- 员工提交假期申请 --><userTask id="submitRequest" name="提交假期申请" /><sequenceFlow id="flow2" sourceRef="submitRequest" targetRef="managerApproval" /><!-- 经理审批 --><userTask id="managerApproval" name="经理审批" /><sequenceFlow id="flow3" sourceRef="managerApproval" targetRef="end" /><!-- 绑定到经理审批的边界事件:如果超时未审批则触发 --><boundaryEvent id="approvalTimeout" attachedToRef="managerApproval"><!-- 计时器中间捕获事件定义:等待3天(72小时) --><timerEventDefinition><timeDuration>PT72H</timeDuration> <!-- 时间格式:PT代表时间周期,72H表示72小时 --></timerEventDefinition></boundaryEvent><sequenceFlow id="flow4" sourceRef="approvalTimeout" targetRef="sendReminder" /><!-- 发送审批提醒 --><serviceTask id="sendReminder" name="发送审批提醒" /><sequenceFlow id="flow5" sourceRef="sendReminder" targetRef="managerApproval" /><endEvent id="end" /></process> </definitions>
流程说明:
- 员工提交假期申请后,流程进入 "经理审批" 环节
- 同时,绑定在审批环节上的计时器开始计时(等待 72 小时)
- 如果经理在 3 天内完成审批,流程直接结束
- 如果 3 天内未审批,计时器事件触发,执行 "发送审批提醒"
- 发送提醒后,流程回到 "经理审批" 环节,等待经理处理
2. 信号/消息中间捕获事件
信号 / 消息中间捕获事件是 BPMN 中用于接收外部信号或消息的机制,与对应的抛出事件配合使用,实现流程间或流程与外部系统的异步通信。其中:
- 消息中间捕获事件:接收特定的点对点消息,通常用于两个明确流程之间的通信
- 信号中间捕获事件:接收广播式信号,可被多个流程实例同时捕获
两者均会使流程暂停,等待对应的事件触发后再继续执行。
消息中间捕获事件实例:订单支付流程
以下是一个电商订单流程的示例,订单创建后会等待支付系统发送的支付完成消息,收到消息后才继续后续的发货流程。
<?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:flowable="http://flowable.org/bpmn"xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL http://www.omg.org/spec/BPMN/2.0/20100524/BPMN20.xsd http://flowable.org/bpmn http://flowable.org/bpmn/current/flowable-bpmn.xsd"id="Definitions_1"targetNamespace="http://flowable.org/examples"><!-- 定义支付消息 --><message id="paymentMsg" name="支付完成消息" /><!-- 订单处理流程 --><process id="orderProcess" name="订单处理流程" isExecutable="true"><startEvent id="start" /><sequenceFlow id="flow1" sourceRef="start" targetRef="createOrder" /><!-- 创建订单 --><serviceTask id="createOrder" name="创建订单" flowable:class="com.example.CreateOrderService" /><sequenceFlow id="flow2" sourceRef="createOrder" targetRef="waitPayment" /><!-- 消息中间捕获事件:等待支付完成消息 --><intermediateCatchEvent id="waitPayment"><messageEventDefinition messageRef="paymentMsg" /></intermediateCatchEvent><sequenceFlow id="flow3" sourceRef="waitPayment" targetRef="arrangeDelivery" /><!-- 安排发货 --><serviceTask id="arrangeDelivery" name="安排发货" flowable:class="com.example.ArrangeDeliveryService" /><sequenceFlow id="flow4" sourceRef="arrangeDelivery" targetRef="end" /><endEvent id="end" /></process><!-- 支付处理流程 --><process id="paymentProcess" name="支付处理流程" isExecutable="true"><startEvent id="paymentStart" /><sequenceFlow id="flow5" sourceRef="paymentStart" targetRef="processPayment" /><!-- 处理支付 --><serviceTask id="processPayment" name="处理支付" flowable:class="com.example.ProcessPaymentService" /><sequenceFlow id="flow6" sourceRef="processPayment" targetRef="sendPaymentMsg" /><!-- 消息中间抛出事件:发送支付完成消息 --><intermediateThrowEvent id="sendPaymentMsg"><messageEventDefinition messageRef="paymentMsg" /></intermediateThrowEvent><sequenceFlow id="flow7" sourceRef="sendPaymentMsg" targetRef="paymentEnd" /><endEvent id="paymentEnd" /></process>
</definitions>
Java 代码实现示例:
- 创建订单服务类:
public class CreateOrderService implements JavaDelegate {@Overridepublic void execute(DelegateExecution execution) {String orderId = "ORDER-" + System.currentTimeMillis();execution.setVariable("orderId", orderId);System.out.println("订单创建成功,订单ID: " + orderId);// 实际业务中会保存订单信息到数据库}
}
- 处理支付服务类:
public class ProcessPaymentService implements JavaDelegate {@Overridepublic void execute(DelegateExecution execution) {String orderId = (String) execution.getVariable("orderId");System.out.println("处理订单 " + orderId + " 的支付...");// 实际业务中会调用支付网关处理支付execution.setVariable("paymentStatus", "SUCCESS");}
}
- 流程交互代码:
// 1. 部署流程
repositoryService.createDeployment().addClasspathResource("order-payment-process.bpmn20.xml").deploy();// 2. 启动订单流程
Map<String, Object> orderVariables = new HashMap<>();
ProcessInstance orderInstance = runtimeService.startProcessInstanceByKey("orderProcess", orderVariables);
String orderId = (String) runtimeService.getVariable(orderInstance.getId(), "orderId");
System.out.println("订单流程已启动,等待支付,订单ID: " + orderId);// 3. 启动支付流程,传入订单ID
Map<String, Object> paymentVariables = new HashMap<>();
paymentVariables.put("orderId", orderId);
ProcessInstance paymentInstance = runtimeService.startProcessInstanceByKey("paymentProcess", paymentVariables);// 4. 支付完成后,发送支付消息给订单流程
// 查找等待支付消息的执行实例
Execution waitingExecution = runtimeService.createExecutionQuery().processInstanceId(orderInstance.getId()).messageEventSubscriptionName("paymentMsg").singleResult();// 发送消息触发订单流程继续执行
runtimeService.messageEventReceived("paymentMsg", waitingExecution.getId());
System.out.println("支付完成消息已发送,订单流程将继续执行");
实例执行流程说明
- 订单流程启动后,创建订单并执行到
waitPayment
节点 - 此时订单流程暂停,等待
paymentMsg
消息 - 支付流程启动并处理支付,完成后通过
sendPaymentMsg
节点抛出paymentMsg
消息 - 调用
runtimeService.messageEventReceived()
方法将消息传递给订单流程 - 订单流程的
waitPayment
节点捕获到消息,继续执行后续的发货流程
关键特性总结
- 消息捕获事件与抛出事件通过
messageRef
关联,必须引用同一个消息 ID - 流程会在消息捕获事件处暂停,直到收到对应的消息才继续
- 消息是点对点的,通常用于两个特定流程之间的通信
- 适用于需要异步等待外部系统响应的场景(如支付、审批等)
2.结束事件
2.1 空结束事件(None End Event)
2.1.1 解释
- 定义:空结束事件是 Flowable 中最常见且最简单的结束事件。它用于标记流程或分支的结束,且不抛出任何特定结果。当流程实例或分支到达空结束事件时,流程引擎会终止该流程实例或分支。如果流程实例有多个并行分支,只有当所有分支都到达空结束事件时,整个流程实例才会结束。
- 特点:
- 不处理抛出结果,相当于流程或分支的“正常结束”。
- 适用于流程或分支的常规终止场景,无需额外的操作。
- 使用场景:通常用于流程的自然结束,例如订单处理完成后结束流程。
2.1.2 图形标记
- 空结束事件在 BPMN 图中表现为一个粗边圆圈,内部没有图标,表示无特定结果类型。
- 示例图形(文字描述):end-event(粗边圆圈,无内部图标)。
2.1.3 XML 表示
- 空结束事件的 XML 定义非常简单,仅包含 <endEvent> 标签,无子元素。
- 示例代码:
<endEvent id="endEvent1" name="noneEndEvent"></endEvent>
- id:事件的唯一标识符。
- name:事件的显示名称(可选)。
2.1.4 使用示例
- 场景:一个简单的订单处理流程,包含下单、支付和发货步骤,支付成功后到达空结束事件,流程结束。
- 流程描述:用户下单后,流程进入支付节点,支付成功后到达空结束事件,流程实例终止。
- XML 示例:
<process id="simpleOrderProcess" name="简单订单流程" isExecutable="true"><startEvent id="startEvent" /><sequenceFlow id="flow1" sourceRef="startEvent" targetRef="orderTask" /><userTask id="orderTask" name="下单" /><sequenceFlow id="flow2" sourceRef="orderTask" targetRef="paymentTask" /><userTask id="paymentTask" name="支付" /><sequenceFlow id="flow3" sourceRef="paymentTask" targetRef="endEvent" /><endEvent id="endEvent" name="noneEndEvent" /></process>
- 说明:流程从 startEvent 开始,经过 orderTask(下单)和 paymentTask(支付),最后到达 endEvent,结束流程。
2.2 错误结束事件(Error End Event)
2.2.1 解释
- 定义:错误结束事件在流程到达时抛出错误,并终止当前流程分支。它通常用于子流程中,抛出的错误会被父流程的错误边界事件捕获。错误结束事件需要定义一个错误码(errorRef),以便与错误边界事件匹配。
- 特点:
- 抛出特定错误码,触发对应的错误边界事件。
- 仅适用于子流程,父流程通过错误边界事件处理抛出的错误。
- 使用场景:常用于异常处理,例如支付失败时触发重新支付的逻辑。
2.2.2 图形标记
- 错误结束事件表现为一个粗边圆圈,内部包含一个全黑错误图标(类似闪电形状),表示抛出错误。
- 示例图形(文字描述):end-event(粗边圆圈,内部有全黑错误图标)。
2.2.3 XML 表示
- 错误结束事件的 XML 定义包含 <endEvent> 标签和 <errorEventDefinition> 子元素,errorRef 属性引用流程中定义的错误。
- 示例代码:
<error id="theError" />
<process id="errorEndEventProcess">
<endEvent id="myErrorEndEvent">
<errorEventDefinition errorRef="theError" />
</endEvent>
</process>
- <error>:定义错误,id 为错误标识。
- <errorEventDefinition>:指定抛出的错误,errorRef 引用错误标识。
2.2.4 使用示例
- 场景:一个订单流程包含支付子流程。如果支付失败,子流程到达错误结束事件,抛出错误,触发父流程的错误边界事件,重新启动支付子流程;如果支付成功,子流程到达空结束事件,继续到发货节点。
- 流程描述:
- 用户下单。
- 进入支付子流程。
- 支付成功:到达空结束事件,子流程结束,进入发货。
- 支付失败:到达错误结束事件,抛出错误,触发错误边界事件,重新进入支付子流程。
- XML 示例(简化版,基于文档提供的内容):
<error id="payError" errorCode="payErrorCode" /><process id="ErrorEndEventProcess" name="错误结束事件" isExecutable="true"><startEvent id="startEvent" /><sequenceFlow id="flow1" sourceRef="startEvent" targetRef="orderTask" /><userTask id="orderTask" name="下单" /><sequenceFlow id="flow2" sourceRef="orderTask" targetRef="paymentSubProcess" /><subProcess id="paymentSubProcess" name="付款子流程"><startEvent id="subStartEvent" /><sequenceFlow id="subFlow1" sourceRef="subStartEvent" targetRef="paymentTask" /><userTask id="paymentTask" name="付款" /><sequenceFlow id="subFlow2" sourceRef="paymentTask" targetRef="gateway" /><exclusiveGateway id="gateway" /><sequenceFlow id="successFlow" name="支付成功" sourceRef="gateway" targetRef="noneEndEvent"><conditionExpression xsi:type="tFormalExpression">${payResult == true}</conditionExpression></sequenceFlow><endEvent id="noneEndEvent" /><sequenceFlow id="errorFlow" name="支付失败" sourceRef="gateway" targetRef="errorEndEvent"><conditionExpression xsi:type="tFormalExpression">${payResult == false}</conditionExpression></sequenceFlow><endEvent id="errorEndEvent"><errorEventDefinition errorRef="payError" /></endEvent></subProcess><sequenceFlow id="flow3" sourceRef="paymentSubProcess" targetRef="deliveryTask" /><userTask id="deliveryTask" name="发货" /><sequenceFlow id="flow4" sourceRef="deliveryTask" targetRef="endEvent" /><endEvent id="endEvent" /><boundaryEvent id="errorBoundaryEvent" attachedToRef="paymentSubProcess"><errorEventDefinition errorRef="payError" /></boundaryEvent><sequenceFlow id="retryFlow" name="重新付款" sourceRef="errorBoundaryEvent" targetRef="paymentSubProcess" /></process>
- 说明:支付子流程 (paymentSubProcess) 中,支付失败时到达 errorEndEvent,抛出 payError 错误,被父流程的 errorBoundaryEvent 捕获,重新流转到 paymentSubProcess。
2.3 取消结束事件(Cancel End Event)
2.3.1 解释
- 定义:取消结束事件专用于 BPMN 事务子流程(Transaction Subprocess)。当到达取消结束事件时,抛出取消事件,必须由事务子流程上的取消边界事件捕获。捕获后,事务被取消,并触发补偿机制(compensation)来回滚相关操作。
- 特点:
- 仅在事务子流程中使用。
- 抛出取消事件,触发取消边界事件和补偿机制。
- 常与补偿事件一起使用,用于事务回滚场景。
- 使用场景:适用于需要事务一致性的场景,例如订单取消后需要释放库存或退款。
2.3.2 图形标记
- 取消结束事件表现为一个粗边圆圈,内部包含一个全黑取消图标(通常为“X”形状)。
- 示例图形(文字描述):cancel-end-event(粗边圆圈,内部有全黑取消图标)。
2.3.3 XML 表示
- 取消结束事件的 XML 定义包含 <endEvent> 标签和 <cancelEventDefinition> 子元素。
- 示例代码:
<process id="cancelEndEventProcess">
<endEvent id="cancelEndEvent">
<cancelEventDefinition />
</endEvent>
</process>
- <cancelEventDefinition>:表示取消事件,无需额外属性。
2.3.4 使用示例
- 场景:一个系统上线事务子流程,包含人工上线任务。如果上线失败,到达取消结束事件,触发取消边界事件,执行自动回滚补偿,并流转到用户排查任务。
- 流程图描述(文字说明):
- 流程启动 → 用户提交订单 → 事务子流程(锁定库存 → 支付)。
- 支付成功 → 空结束事件 → 发货 → 结束。
- 用户取消订单 → 取消结束事件 → 触发取消边界事件 → 补偿(释放库存、退款) → 自动取消订单 → 结束。
- XML 示例:
<process id="CompleteTransactionSubProcess" name="完整事务子流程" isExecutable="true"><!-- 错误定义 --><error id="errorFlag" errorCode="500" /><!-- 主流程 --><startEvent id="startEvent" /><sequenceFlow id="flow1" sourceRef="startEvent" targetRef="submitOrderTask" /><userTask id="submitOrderTask" name="用户提交订单"><extensionElements><flowable:formData /><flowable:assigneeType>static</flowable:assigneeType></extensionElements></userTask><sequenceFlow id="flow2" sourceRef="submitOrderTask" targetRef="transactionSubProcess" /><!-- 事务子流程 --><transaction id="transactionSubProcess" name="订单处理事务"><startEvent id="subStartEvent" /><sequenceFlow id="subFlow1" sourceRef="subStartEvent" targetRef="lockInventoryTask" /><serviceTask id="lockInventoryTask" name="锁定库存" flowable:class="com.example.LockInventoryService"><extensionElements><flowable:formData /><flowable:assigneeType>static</flowable:assigneeType></extensionElements></serviceTask><sequenceFlow id="subFlow2" sourceRef="lockInventoryTask" targetRef="paymentTask" /><userTask id="paymentTask" name="用户支付订单"><extensionElements><flowable:formData /><flowable:assigneeType>static</flowable:assigneeType></extensionElements></userTask><sequenceFlow id="subFlow3" sourceRef="paymentTask" targetRef="gateway" /><exclusiveGateway id="gateway" /><!-- 正常路径:支付成功 --><sequenceFlow id="successFlow" name="支付成功" sourceRef="gateway" targetRef="deductInventoryTask"><conditionExpression xsi:type="tFormalExpression">${payResult == true}</conditionExpression></sequenceFlow><serviceTask id="deductInventoryTask" name="扣减库存" flowable:class="com.example.DeductInventoryService" /><sequenceFlow id="subFlow4" sourceRef="deductInventoryTask" targetRef="noneEndEvent" /><endEvent id="noneEndEvent" /><!-- 取消路径:用户取消订单 --><sequenceFlow id="cancelFlow" name="用户取消" sourceRef="gateway" targetRef="cancelEndEvent"><conditionExpression xsi:type="tFormalExpression">${payResult == false}</conditionExpression></sequenceFlow><endEvent id="cancelEndEvent"><cancelEventDefinition /></endEvent><!-- 补偿任务 --><serviceTask id="releaseInventoryTask" name="释放库存" isForCompensation="true" flowable:class="com.example.ReleaseInventoryService" /><serviceTask id="refundTask" name="费用退回" isForCompensation="true" flowable:class="com.example.RefundService" /><boundaryEvent id="compensateInventoryEvent" attachedToRef="lockInventoryTask"><compensateEventDefinition /></boundaryEvent><boundaryEvent id="compensatePaymentEvent" attachedToRef="paymentTask"><compensateEventDefinition /></boundaryEvent><association id="assoc1" associationDirection="One" sourceRef="compensateInventoryEvent" targetRef="releaseInventoryTask" /><association id="assoc2" associationDirection="One" sourceRef="compensatePaymentEvent" targetRef="refundTask" /></transaction><!-- 取消边界事件 --><boundaryEvent id="cancelBoundaryEvent" attachedToRef="transactionSubProcess"><cancelEventDefinition /></boundaryEvent><sequenceFlow id="flow3" sourceRef="cancelBoundaryEvent" targetRef="cancelOrderTask" /><serviceTask id="cancelOrderTask" name="自动取消订单" flowable:class="com.example.CancelOrderService"><extensionElements><flowable:formData /><flowable:assigneeType>static</flowable:assigneeType></extensionElements></serviceTask><sequenceFlow id="flow4" sourceRef="cancelOrderTask" targetRef="endEventCancel" /><endEvent id="endEventCancel" /><!-- 正常路径继续 --><sequenceFlow id="flow5" sourceRef="transactionSubProcess" targetRef="deliveryTask" /><userTask id="deliveryTask" name="发货"><extensionElements><flowable:formData /><flowable:assigneeType>static</flowable:assigneeType></extensionElements></userTask><sequenceFlow id="flow6" sourceRef="deliveryTask" targetRef="endEventSuccess" /><endEvent id="endEventSuccess" /> </process>
- 正常路径:
- 从 subStartEvent 开始,执行 lockInventoryTask(锁定库存)和 paymentTask(支付)。
- 支付成功(payResult == true),通过 gateway 流向 deductInventoryTask(扣减库存),到达 noneEndEvent,事务子流程结束,进入主流程的 deliveryTask(发货)。
- 取消路径:
- 用户取消订单(payResult == false),通过 gateway 流向 cancelEndEvent,抛出取消事件。
- cancelBoundaryEvent 捕获取消事件,触发补偿任务(releaseInventoryTask 和 refundTask),释放库存并退款。
- 流程流向 cancelOrderTask(自动取消订单),最终到达 endEventCancel。
- 补偿机制:
- releaseInventoryTask 和 refundTask 标记为 isForCompensation="true",通过 compensateEventDefinition 关联到对应的任务,确保取消时回滚操作。
- 图示
[主流程]|▼
[Start Event] ----> [UserTask: 用户提交订单]| name="submitOrderTask"| id="submitOrderTask"▼
[Transaction Subprocess: 订单处理事务] ----> [UserTask: 发货] ----> [End Event]id="transactionSubProcess" name="deliveryTask" id="endEventSuccess"| id="deliveryTask"▼[Start Event] ----> [ServiceTask: 锁定库存] ----> [UserTask: 用户支付订单] ----> [Exclusive Gateway]id="subStartEvent" name="lockInventoryTask" name="paymentTask" id="gateway"id="lockInventoryTask" id="paymentTask"flowable:class="com.example.LockInventoryService"输出参数: payResult (boolean)|| payResult == true▼[ServiceTask: 扣减库存] ----> [None End Event]name="deductInventoryTask" id="noneEndEvent"id="deductInventoryTask"flowable:class="com.example.DeductInventoryService"|| payResult == false▼[Cancel End Event] ----> [触发 Cancel Boundary Event]id="cancelEndEvent" id="cancelBoundaryEvent"<cancelEventDefinition /> attachedTo="transactionSubProcess"|| [Compensate Boundary Event] ----> [ServiceTask: 释放库存]| id="compensateInventoryEvent" name="releaseInventoryTask"| attachedTo="lockInventoryTask" isForCompensation="true"| flowable:class="com.example.ReleaseInventoryService"|| [Compensate Boundary Event] ----> [ServiceTask: 费用退回]| id="compensatePaymentEvent" name="refundTask"| attachedTo="paymentTask" isForCompensation="true"| flowable:class="com.example.RefundService"|▼
[Cancel Boundary Event] ----> [ServiceTask: 自动取消订单] ----> [End Event]id="cancelBoundaryEvent" name="cancelOrderTask" id="endEventCancel"attachedTo="transactionSubProcess" id="cancelOrderTask"<cancelEventDefinition /> flowable:class="com.example.CancelOrderService"
2.3.5 注意事项
- 取消结束事件只能用于事务子流程。
- 必须有取消边界事件捕获取消事件。
- 取消事件需要配合补偿机制,否则会抛出异常(如 FlowableException: No execution found for sub process of boundary cancel event)。
2.4 终止结束事件(Terminate End Event)
2.4.1 解释
- 定义:终止结束事件用于强制终止整个流程或其所在的范围(scope,如子流程或主流程)。当流程到达终止结束事件时,流程引擎会终止当前范围内的所有活动,包括并行分支。如果在子流程中,终止范围由 terminateAll 属性决定。
- 特点:
- 可终止整个流程或仅当前子流程。
- terminateAll="true":终止整个根流程实例,无论事件位置。
- terminateAll="false"(默认):仅终止当前子流程或实例。
- 使用场景:适用于需要立即停止所有流程活动的场景,例如紧急取消订单。
2.4.2 图形标记
- 终止结束事件表现为一个粗边圆圈,内部包含一个全黑实心圆图标。
- 示例图形(文字描述):terminate-end-event(粗边圆圈,内部有全黑实心圆)。
2.4.3 XML 表示
- 终止结束事件的 XML 定义包含 <endEvent> 标签和 <terminateEventDefinition> 子元素,flowable:terminateAll 属性控制终止范围。
- 示例代码:
<endEvent id="terminateEndEvent">
<terminateEventDefinition flowable:terminateAll="true" />
</endEvent>
- flowable:terminateAll:可选,true 表示终止整个流程,false 表示仅终止当前范围。
2.4.4 使用示例
- 场景:一个订单流程包含并行分支(支付和库存锁定)。如果用户取消订单,到达终止结束事件,立即终止所有分支,流程结束。
- 流程描述:
- 流程启动,进入并行网关,分为支付和库存锁定两个分支。
- 用户取消订单,到达终止结束事件,终止所有分支,流程结束。
- XML 示例:
<process id="terminateEndEventProcess" name="终止结束事件" isExecutable="true"><startEvent id="startEvent" /><sequenceFlow id="flow1" sourceRef="startEvent" targetRef="orderTask" /><userTask id="orderTask" name="下单" /><sequenceFlow id="flow2" sourceRef="orderTask" targetRef="parallelGateway" /><parallelGateway id="parallelGateway" /><sequenceFlow id="flow3" sourceRef="parallelGateway" targetRef="paymentTask" /><userTask id="paymentTask" name="支付" /><sequenceFlow id="flow4" sourceRef="paymentTask" targetRef="noneEndEvent" /><endEvent id="noneEndEvent" /><sequenceFlow id="flow5" sourceRef="parallelGateway" targetRef="lockTask" /><serviceTask id="lockTask" name="锁定库存" flowable:class="com.example.LockService" /><sequenceFlow id="flow6" sourceRef="lockTask" targetRef="cancelTask" /><userTask id="cancelTask" name="取消订单" /><sequenceFlow id="flow7" sourceRef="cancelTask" targetRef="terminateEndEvent" /><endEvent id="terminateEndEvent"><terminateEventDefinition flowable:terminateAll="true" /></endEvent></process>
- 说明:流程通过 parallelGateway 分出支付和库存锁定分支。如果用户在 cancelTask 取消订单,到达 terminateEndEvent,设置 terminateAll="true",整个流程(包括支付分支)立即终止。
2.4.5 注意事项
- terminateAll="true" 会终止整个流程实例,适用于需要强制停止所有活动的场景。
- terminateAll="false" 仅终止当前子流程,适合局部终止场景。
总结
- 空结束事件:最简单的结束事件,用于流程或分支的正常结束,无需抛出结果。
- 错误结束事件:用于子流程抛出错误,触发父流程的错误边界事件,适合异常处理。
- 取消结束事件:专用于事务子流程,触发取消边界事件和补偿机制,适合事务回滚。
- 终止结束事件:强制终止流程或子流程,适合紧急停止场景,范围由 terminateAll 属性控制。
- 共同点:所有结束事件均为“抛出”事件,区别在于抛出结果的类型和处理方式。