解锁Flowable高阶玩法:任务监听器与多实例的深度实战指南
当你在Flowable流程设计器中熟练地拖拽节点、连接线条时,是否曾思考过如何让静态的流程图真正"活"起来?本文将带你突破基础绘制的局限,通过一个完整的"员工报销+多部门会签"案例,揭秘任务监听器与多实例这两项核心技术的实战应用。
1. 任务监听器:让流程动态化的秘密武器
任务监听器是Flowable中最强大的动态行为控制器之一。它允许开发者在任务生命周期的关键节点注入自定义逻辑,实现审批人动态分配、业务事件触发等复杂场景。
1.1 四大监听器类型解析
- create监听器:任务创建时触发,常用于动态设置处理人
- assignment监听器:任务被签收时执行,适合记录接单时间等场景
- complete监听器:任务完成时激活,可触发后续业务逻辑
- delete监听器:任务删除前执行,用于资源清理等操作
// 典型create监听器实现示例 public class DynamicAssigneeListener implements TaskListener { @Override public void notify(DelegateTask task) { String department = (String)task.getVariable("dept"); String assignee = userService.findDeptManager(department); task.setAssignee(assignee); } }1.2 报销案例中的动态审批人分配
假设我们的报销流程需要根据员工部门自动分配审批人:
- 在用户任务节点添加create监听器
- 配置监听器类为
com.example.ExpenseApprovalListener - 监听器通过部门ID查询对应审批人
提示:监听器类需实现TaskListener接口,并部署到流程引擎的classpath中
表:监听器配置参数对比
| 参数 | 说明 | 示例值 |
|---|---|---|
| event | 监听器类型 | create |
| class | 实现类全限定名 | com.example.ExpenseApprovalListener |
| expression | EL表达式 | ${taskListenerBean.dynamicAssign} |
| delegateExpression | 委托表达式 | ${delegateTaskListener} |
2. 多实例:复杂审批模式的终极解决方案
多实例特性允许单个节点在运行时生成多个任务实例,完美支持会签、或签等复杂审批场景。
2.1 串行与并行模式选择
- 串行(Sequential):按顺序依次审批,前一人通过后才触发下一人
- 并行(Parallel):同时发送给所有审批人,独立处理
<!-- 并行多实例配置示例 --> <userTask id="multiApproval" name="多部门会签"> <multiInstanceLoopCharacteristics isSequential="false"> <loopCardinality>3</loopCardinality> <completionCondition>${nrOfCompletedInstances/nrOfInstances >= 0.6}</completionCondition> </multiInstanceLoopCharacteristics> </userTask>2.2 报销流程中的多部门会签实现
当报销金额超过1万元时,需要财务、法务、采购三个部门会签:
- 设置多实例节点为并行模式
- 配置基数(loopCardinality)为3
- 定义完成条件(completionCondition)为"60%通过即可"
关键变量说明:
nrOfInstances:总实例数nrOfActiveInstances:活跃实例数nrOfCompletedInstances:已完成实例数loopCounter:当前迭代索引
3. 监听器与多实例的协同作战
将两种技术结合使用,可以构建出极其灵活的审批流程。例如在会签场景中:
- 用多实例创建多个审批任务
- 为每个任务添加complete监听器,在审批通过时:
- 记录审批意见
- 更新进度看板
- 触发通知下一处理人
// 会签complete监听器示例 public class CountersignCompleteListener implements TaskListener { @Override public void notify(DelegateTask task) { // 获取当前审批结果 Boolean approved = (Boolean)task.getVariable("approved"); // 更新审批统计 Map<String, Integer> stats = (Map)task.getVariable("approvalStats"); stats.put(approved ? "approved" : "rejected", stats.getOrDefault(approved ? "approved" : "rejected", 0) + 1); // 如果拒绝数超过阈值,直接终止流程 if(stats.getOrDefault("rejected", 0) > 1) { task.getExecution().setVariable("terminateFlow", true); } } }4. 调试技巧与性能优化
即使是最有经验的开发者,面对复杂流程时也难免遇到问题。以下是几个实用技巧:
4.1 常见问题排查清单
监听器不触发:
- 检查事件类型是否匹配
- 确认类路径配置正确
- 查看流程定义是否最新版本
多实例卡住:
- 验证完成条件表达式
- 检查实例计数器变量
- 确认并行/串行模式设置
变量传递问题:
- 使用
execution.setVariable()而非task.setVariable() - 多实例中注意变量作用域
- 使用
4.2 性能优化建议
- 为高频调用的监听器添加缓存
- 避免在监听器中执行耗时IO操作
- 对大基数多实例考虑分批处理
- 使用异步监听器(
flowable:async="true")
-- 监控多实例性能的查询示例 SELECT PROC_DEF_ID_, ACT_ID_, COUNT(*) FROM ACT_RU_TASK WHERE PROC_INST_ID_ = #{processInstanceId} GROUP BY PROC_DEF_ID_, ACT_ID_;在实际项目中,我发现最有效的调试方式是结合流程实例ID查询运行时任务表(ACT_RU_TASK)和历史表(ACT_HI_TASKINST),可以清晰看到任务流转轨迹和变量变化。当遇到复杂的多实例逻辑时,先在测试环境用小基数验证完成条件表达式,确认无误后再应用到正式流程中。