1. 为什么你需要掌握Flowable多实例
第一次接触Flowable多实例时,我正面临一个棘手的审批场景:某跨国项目的采购申请需要经过不同地区负责人的并行审批。传统做法是为每个地区硬编码一个审批节点,这不仅让流程图变得臃肿,更致命的是每次新增地区都要重新部署流程。直到发现多实例特性,才真正体会到什么叫"动态工作流"的魅力。
多实例本质上是一种智能循环机制,它允许我们:
- 动态生成任务:根据运行时数据(如审批人列表)自动创建对应数量的任务实例
- 灵活控制流程:支持并行/串行执行,可设置智能完成条件(如"过半通过即生效")
- 减少模板代码:用配置代替硬编码,使流程具备适应业务变化的能力
在实际项目中,我常用它处理这些场景:
- 动态审批链(如根据金额自动匹配审批层级)
- 批量数据处理(如同时向多个系统发起数据同步)
- 弹性任务分发(如客服工单的智能负载均衡)
2. 多实例的核心配置详解
2.1 基础配置三要素
配置一个多实例活动需要掌握这三个关键属性:
<userTask id="multiTask" name="动态审批" flowable:assignee="${approver}"> <multiInstanceLoopCharacteristics isSequential="false" flowable:collection="${approverService.getApprovers(execution)}" flowable:elementVariable="approver"> <completionCondition>${nrOfCompletedInstances >= 2}</completionCondition> </multiInstanceLoopCharacteristics> </userTask>collection:最强大的动态数据源配置,支持:
- 直接指定集合变量名(如
approverList) - 使用Spring Bean方法调用(如示例中的
approverService) - 支持EL表达式实现运行时计算
- 直接指定集合变量名(如
elementVariable:当前元素的引用变量,在任务分配、条件判断中频繁使用
isSequential:布尔值决定并行(false)/串行(true)模式。曾经在报销流程中踩过坑:误将财务复核设为并行模式,导致出纳同时收到多个冲突请求
2.2 智能完成条件实战
默认情况下,多实例会等待所有任务完成。但通过completionCondition可以实现更灵活的流程控制:
<!-- 过半通过即生效 --> <completionCondition> ${nrOfCompletedInstances/nrOfInstances >= 0.5} </completionCondition> <!-- 一票否决场景 --> <completionCondition> ${nrOfCompletedInstances > 0 && hasReject} </completionCondition>我曾用这种机制实现过智能投票系统:
- 当赞成票达到60%时立即通过
- 反对票超过30%时自动终止流程
- 72小时未达成条件则进入人工仲裁
3. 动态数据绑定的高阶玩法
3.1 运行时数据注入
在多实例场景中,我经常需要将上下文数据传递给每个实例。比如在合同审批流程中,需要让每个审批人看到完整的合同历史:
// 在流程启动前注入基础数据 runtimeService.setVariable(processInstanceId, "contractHistory", getContractVersions()); // 在服务任务中动态补充审批人 public List<String> getApproversWithContext(DelegateExecution execution) { Contract contract = (Contract) execution.getVariable("contract"); return approverDao.findByDepartment(contract.getDepartment()); }3.2 多实例与监听器的组合技
通过事件监听器可以实现更精细的控制:
// 在每个实例创建时追加专属数据 @EventListener public void onTaskCreated(DelegateTask task) { if (task.getProcessDefinitionId().contains("multiInstance")) { String approver = (String) task.getVariable("approver"); task.setVariableLocal("approverHistory", getApprovalHistory(approver)); } }这种模式特别适合需要审计追踪的场景。最近一个银行客户的项目中,我们用它实现了完整的审批轨迹记录,每个审批步骤都自动关联了当时的系统快照。
4. 避坑指南与性能优化
4.1 常见问题排查
在实施过程中,这几个问题最常出现:
集合数据为空:建议添加前置校验
<sequenceFlow id="checkApprovers" sourceRef="gateway" targetRef="multiTask"> <conditionExpression xsi:type="tFormalExpression"> ${!CollectionUtils.isEmpty(approverList)} </conditionExpression> </sequenceFlow>变量作用域混淆:注意区分流程变量与本地变量
- 父执行实例:存储
nrOfInstances等全局数据 - 子执行实例:通过
elementVariable访问当前元素
- 父执行实例:存储
事务边界问题:并行任务数量过大时可能触发数据库锁超时
4.2 性能优化建议
处理过的一个生产案例:当审批人超过500时,流程启动耗时从200ms飙升到8秒。通过以下优化方案最终控制在1秒内:
分批加载:改造集合获取逻辑,采用分页查询
public List<String> getApproversChunk(DelegateExecution exec) { int chunkSize = 100; int offset = (int) exec.getVariable("chunkIndex") * chunkSize; return approverDao.findChunk(offset, chunkSize); }异步化处理:对非强一致性的任务启用异步特性
<multiInstanceLoopCharacteristics isSequential="false" flowable:async="true">缓存集合数据:避免重复查询相同数据源
5. 真实案例:智能工单分发系统
去年为某电商平台设计的客服工单系统,完美展现了多实例的实战价值:
业务需求:
- 根据工单类型自动匹配技能组(售后、技术、投诉等)
- 每个技能组内按负载均衡分配坐席
- 30秒无响应自动升级到组长
解决方案:
<serviceTask id="dispatchTask" flowable:class="com.example.TicketDispatcher"> <multiInstanceLoopCharacteristics isSequential="false" flowable:collection="${ticketRouter.getTargetGroups(execution)}" flowable:elementVariable="targetGroup"> <completionCondition> ${ticketDispatcher.hasAccepted(execution) || ticketDispatcher.timeout(execution)} </completionCondition> </multiInstanceLoopCharacteristics> </serviceTask>关键实现点:
- 通过
ticketRouter动态解析工单类型对应的技能组 - 每个技能组并行执行坐席选择逻辑
- 完成条件检查是否有坐席接单或超时
上线后平均响应时间从原来的4分钟缩短到22秒,客户满意度提升35%。这个案例充分证明,合理运用多实例特性可以让死板的流程"活"起来。