为什么要改造?

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 源码分析。有帮助的话欢迎点赞收藏。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐