Flowable CallActivityBehavior 源码改造实战:注册机制 + 4 个覆写点 + 踩坑记录
为什么要改造?
CallActivity(子流程)是 Flowable 父子流程串联的核心节点,但默认的 CallActivityBehavior 在真实业务中不够用:子流程发起人怎么定?变量怎么默认填充?发版后旧定义挂起导致子流程完成报错怎么办?
答案都在 CallActivityBehavior 的 4 个可覆写方法里。先讲怎么注册,再逐一拆解。
一、注册机制:让 Flowable 用你的 Behavior
Flowable 解析 BPMN 时,每遇到一个节点就调用 ActivityBehaviorFactory 的对应方法创建 “行为对象”。我们要做的就两步:
Step 1:继承 DefaultActivityBehaviorFactory,覆写 createCallActivityBehavior
public class CustomActivityBehaviorFactory extends DefaultActivityBehaviorFactory {
private CandidateInvoker candidateInvoker;
private LogService logService;
public CustomActivityBehaviorFactory(CandidateInvoker candidateInvoker, LogService logService) {
this.candidateInvoker = candidateInvoker;
this.logService = logService;
}
@Override
public CallActivityBehavior createCallActivityBehavior(CallActivity callActivity) {
return new CustomCallActivityBehavior(callActivity)
.setCandidateInvoker(candidateInvoker)
.setLogService(logService);
}
}
Step 2:通过 EngineConfigurationConfigurer 注入引擎
@Configuration
public class FlowableEngineConfig implements EngineConfigurationConfigurer<SpringProcessEngineConfiguration> {
@Resource private CandidateInvoker candidateInvoker;
@Resource private LogService logService;
@Override
public void configure(SpringProcessEngineConfiguration config) {
config.setActivityBehaviorFactory(
new CustomActivityBehaviorFactory(candidateInvoker, logService));
}
}
关键点:Behavior 不是 Spring Bean,需要用到的 Service 通过工厂的 Setter 注入。拿到工厂,你就拿到了所有节点行为的控制权。
二、子流程创建阶段:execute() + initializeVariables()
生命周期:
execute() → initializeVariables() → 子流程开始运行
↑ 创建入口 ↑ 变量初始化
2.1 execute() — 三个改造点
@Override
public void execute(DelegateExecution execution) {
ExecutionEntity exec = (ExecutionEntity) execution;
// 改造 1:测试流程直接跳过,不创建子流程
if (isTestProcess(exec)) { leave(execution); return; }
// 改造 2:用户审批时勾选"跳过子流程",记录日志后跳过
if (isIgnoreSubCreate(exec)) {
saveSkipLog(exec);
leave(execution);
return;
}
// 改造 3:占位符技巧——父类 execute() 只在 inParameters 不为空时
// 才调用 initializeVariables(),塞一个占位符确保它一定执行
List<IOParameter> inParams = callActivity.getInParameters();
if (inParams.isEmpty()) {
IOParameter ph = new IOParameter();
ph.setSource("__placeholder__"); ph.setTarget("__placeholder__");
inParams.add(ph);
}
super.execute(execution); // 走标准创建流程
}
跳过时调用
leave(execution),父流程继续往后走到下一个节点。
2.2 initializeVariables() — 给子流程填默认变量
父类这个方法只有一行 subProcessInstance.setVariables(variables)。覆写它来填充系统变量:
@Override
protected void initializeVariables(ExecutionEntity subProcessInstance,
Map<String, Object> variables) {
ExecutionEntity parentExec = subProcessInstance.getSuperExecution();
variables.remove("__placeholder__"); // 移除 execute() 塞的占位符
// 1 发起人:优先取 CallActivity 节点审批人策略,兜底用父流程发起人
variables.put("creator", calculateInitiator(parentExec));
// 2 部门、分类、名称 从父流程和定义扩展信息继承
Map<String, Object> pv = parentExec.getVariables();
variables.put("creatorDeptId", pv.get("creatorDeptId"));
variables.put("category", getExtInfo(subProcessInstance).getCategoryCode());
variables.put("startName", getExtInfo(subProcessInstance).getProcessStartName());
// 3 系统状态
variables.put("status", "RUNNING");
variables.put("isSubmitted", true);
// 4 记录父流程节点 ID,方便回溯
CallActivity ca = (CallActivity) parentExec.getCurrentFlowElement();
variables.put("parentCallActivityId", ca.getId());
// 5 记录创建日志(含入参快照)
saveCallActivityLog(parentExec, "CALL_ACTIVITY_CREATE",
ca.getProcessInstanceName(), subProcessInstance.getId(),
callActivity.getInParameters(), null, parentExec.getVariables());
super.initializeVariables(subProcessInstance, variables);
}
// 发起人策略:多实例 assignee → 候选人策略 → 父流程发起人
private Object calculateInitiator(DelegateExecution execution) { ... }
三、子流程完成阶段:completing() + completed()
生命周期:
子流程结束 → completing() → completed() → 父流程继续
↑ 数据反写 ↑ 控制流转
3.1 completing() — 加完成日志
@Override
public void completing(DelegateExecution execution, DelegateExecution subProcessInstance) throws Exception {
CallActivity ca = (CallActivity) execution.getCurrentFlowElement();
// 记录完成日志 + 出参快照,方便审计
saveCallActivityLog(execution, "CALL_ACTIVITY_COMPLETE",
ca.getProcessInstanceName(), subProcessInstance.getId(),
null, callActivity.getOutParameters(), subProcessInstance.getVariables());
super.completing(execution, subProcessInstance); // 标准数据反写
}
3.2 completed() — 踩坑与修复(重点)
先看 Flowable 7.0.1 源码:
// CallActivityBehavior.java
public void completed(DelegateExecution execution) throws Exception {
ExecutionEntity executionEntity = (ExecutionEntity) execution;
if (executionEntity.isSuspended() // ① 执行实例挂起检查
|| ProcessDefinitionUtil.isProcessDefinitionSuspended( // ② 流程定义挂起检查
execution.getProcessDefinitionId())) {
throw new FlowableException(
"Cannot complete process instance. Parent process instance "
+ executionEntity + " is suspended");
}
leave(execution);
}
踩坑:明明实例没挂起,怎么报 “is suspended”?
生产环境在发版后批量报这个错。查 act_ru_execution:
SELECT ID_, SUSPENSION_STATE_ FROM act_ru_execution WHERE PROC_INST_ID_ = '5760937';
-- 结果:SUSPENSION_STATE_ 全是 1(激活) ← 实例没挂起!
再查 act_re_procdef:
SELECT ID_, SUSPENSION_STATE_, VERSION_ FROM act_re_procdef WHERE ID_ = 'xxx:32:5756659';
-- 结果:SUSPENSION_STATE_ = 2(挂起) ← 定义被挂了!
因果链:发版部署 V33 → V32 自动挂起 → V32 下仍在运行的子流程完成 → completed() 检查 ② 命中 → 抛异常。
错误消息写 “process instance is suspended” 其实不准确,真凶是 ProcessDefinitionUtil.isProcessDefinitionSuspended()。
修复:只保留执行实例挂起检查
@Override
public void completed(DelegateExecution execution) {
ExecutionEntity executionEntity = (ExecutionEntity) execution;
// 去掉了 ProcessDefinitionUtil.isProcessDefinitionSuspended() 检查
// 发版挂起的是定义不是实例,不应阻止已在运行的子流程完成回调
if (executionEntity.isSuspended()) {
throw new FlowableException(
"Cannot complete process instance. Parent process instance "
+ executionEntity + " is suspended");
}
leave(execution);
}
为什么安全:completed() 只做控制流转(源码注释:only control flow. no sub process instance data available),数据反写在 completing() 中已完成。leave() 是父类 protected 方法,继承链上直接可用。
不需要改另外两个方法:
| 方法 | 挂起检查 | 要改? | 原因 |
|---|---|---|---|
execute() |
✅ 检查子流程定义 | ❌ | 子流程定义挂起时确实不该创建新实例 |
completing() |
❌ 无 | ❌ | 纯数据操作 |
completed() |
✅ 两个条件 OR | ✅ | 定义挂起不应拦已在运行的实例 |
四、操作日志:记录父子流程参数传递快照
前面反复调用的 saveCallActivityLog,核心思路是把 inParameters / outParameters 的值拍个快照:
private void saveCallActivityLog(DelegateExecution execution, String type,
String processName, String childId,
List<IOParameter> inParams, List<IOParameter> outParams,
Map<String, Object> variables) {
Map<String, Object> record = new LinkedHashMap<>();
if (CollUtil.isNotEmpty(inParams))
record.put("inParameters", buildParamList(inParams, variables));
if (CollUtil.isNotEmpty(outParams))
record.put("outParameters", buildParamList(outParams, variables));
OperationLog log = new OperationLog()
.setType(type).setProcessInstanceId(execution.getProcessInstanceId())
.setTaskName(callActivity.getName())
.setDataRecordContent(JSONUtil.toJsonStr(record));
logService.save(log);
}
private List<Map<String, Object>> buildParamList(List<IOParameter> params,
Map<String, Object> variables) {
List<Map<String, Object>> list = new ArrayList<>();
for (IOParameter p : params) {
if ("__placeholder__".equals(p.getSource())) continue;
Map<String, Object> item = new LinkedHashMap<>();
item.put("source", p.getSource());
item.put("target", p.getTarget());
item.put("value", variables.get(p.getSource())); // ★ 参数实际值快照
list.add(item);
}
return list;
}
有了这个,排查 “子流程的变量为什么是这个值” 时直接查日志就行。
五、总结
| 方法 | 时机 | 职责 | 改造场景 |
|---|---|---|---|
execute() |
到达 CallActivity | 创建子流程 | 条件跳过、测试绕过、占位符确保初始化 |
initializeVariables() |
子流程创建后 | 初始化变量 | 发起人/部门/分类/状态填充 |
completing() |
子流程结束时 | 数据反写 | 完成日志 + 参数快照 |
completed() |
子流程结束后 | 控制流转 | 去掉流程定义挂起检查 |
注册链路:自定义 Behavior → 自定义 Factory → config.setActivityBehaviorFactory(),三行代码拿到节点行为控制权。
排查技巧:遇到 “Parent process instance is suspended”,别只看 act_ru_execution,顺手查 act_re_procdef.SUSPENSION_STATE_,十有八九是定义被挂起了。
基于 Flowable 7.0.1 源码分析。有帮助的话欢迎点赞收藏。
更多推荐




所有评论(0)