"Flowable 工作流引擎进阶实战(高级篇):任务分配、流程变量与监听器"

Flowable 工作流引擎进阶实战(高级篇)

本文承接《Flowable 工作流引擎入门教程(基础篇)》,深入任务分配、流程变量、监听器等进阶主题,并补充多实例、网关策略与历史查询等实战必会内容。

入门篇我们跑通了一个「经理审批」的请假流程,但现实业务的复杂度远不止如此:审批人可能是动态的、流程要携带业务数据、某些节点要在创建时自动指定负责人。这些都要靠本篇的进阶特性解决。

一、任务分配与流程变量

1.1 固定分配

最简单的方式:在画流程图或 BPMN 文件里直接写死 assignee

xml 复制代码
<userTask id="approveTask" name="经理审批" flowable:assignee="manager"/>

缺点是人固定死了,换个人就得改流程定义,只适合 demo。

1.2 表达式分配(UEL)

Flowable 使用 UEL(Unified Expression Language) 解析表达式,支持两种形式:

  • 值表达式(Value expression):解析为一个值
  • 方法表达式(Method expression):调用一个方法
scss 复制代码
${myVar}            // 值表达式,读取流程变量
${myBean.myProperty}
${printer.print()}                  // 方法表达式,无参要带空括号
${myBean.addNewOrder('orderName')}  // 带字面量参数
${myBean.doSomething(myVar, execution)}

${myBean} 默认指向 Spring 容器里的 Bean(需集成 Spring)。不集成 Spring 时,可通过 ProcessEngineConfiguration 注册自定义解析器。

值表达式实战:在 BPMN 里把审批人写成变量,启动时绑定:

java 复制代码
@Test
public void testRunProcess() {
    ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
    RuntimeService runtimeService = processEngine.getRuntimeService();

    Map<String, Object> variables = new HashMap<>();
    variables.put("assignee0", "张三");
    variables.put("assignee1", "李四");

    ProcessInstance instance = runtimeService
            .startProcessInstanceById("MyHolidayUI:1:4", variables);
    System.out.println("流程定义ID:" + instance.getProcessDefinitionId());
    System.out.println("流程实例ID:" + instance.getId());
}

启动后查 ACT_RU_TASK 表,会发现任务的 assignee 已是 张三------说明 UEL 表达式被成功解析。

1.3 监听器分配

监听器能在流程运行到特定节点时触发自定义 Java 逻辑,连 assignee 都不用在流程定义里写。最适合「按业务规则动态指定负责人」。

先实现 TaskListener

java 复制代码
public class MyTaskListener implements TaskListener {
    @Override
    public void notify(DelegateTask delegateTask) {
        System.out.println("监听器触发了:" + delegateTask.getName());
        if ("提交请假流程".equals(delegateTask.getName())
                && "create".equals(delegateTask.getEventName())) {
            delegateTask.setAssignee("小明");
        } else {
            delegateTask.setAssignee("小张");
        }
    }
}

监听器可绑定的事件类型:

事件 触发时机
create 任务创建后
assignment 任务被分配后
complete 任务完成后
delete 任务被删除后
all 以上全部

在 Flowable UI(Modeler)里把监听器关联到用户任务即可。部署运行后,Task 表的 assignee 会变成 小明,证明监听器生效。

二、流程变量详解

流程执行时携带的数据在 Flowable 里叫变量(variable),持久化在数据库。它有两个主要用途:

  1. 表达式/网关 里判断走向(如 approved == true 走通过分支);
  2. 服务任务里作为外部调用的入参或结果存储。

流程实例、用户任务、执行(execution)都可以持有变量,每个变量对应 ACT_RU_VARIABLE 表的一行。

2.1 设置变量的 API

启动时设置:

java 复制代码
ProcessInstance startProcessInstanceByKey(String processDefinitionKey,
                                          Map<String, Object> variables);

运行时追加/修改:

java 复制代码
void setVariable(String executionId, String variableName, Object value);
void setVariableLocal(String executionId, String variableName, Object value);
void setVariables(String executionId, Map<String, ? extends Object> variables);
void setVariablesLocal(String executionId, Map<String, ? extends Object> variables);

2.2 全局变量 vs 局部变量

类型 作用域 特点
Global 整个流程实例 变量名不可重复,后设覆盖先设
Local 单个任务/执行实例 各任务互不影响,可与 global 同名共存

举例:出差申请里,userId=zhangsan 是全局变量;而某个会签节点里「本部门主管意见」这种只在本节点有意义的数据,就该用 Local,避免污染流程级命名空间。

2.3 实战案例:出差申请

需求:员工提交出差申请单 → 部门经理审批 → 根据金额走不同分支(≤5000 直接通过,>5000 需总监审批)→ 财务备案。

java 复制代码
@Test
public void testBusinessTrip() {
    RuntimeService rs = processEngine.getRuntimeService();
    Map<String, Object> vars = new HashMap<>();
    vars.put("applicant", "王五");
    vars.put("amount", 8000);          // 金额决定分支
    vars.put("assigneeManager", "经理A");
    vars.put("assigneeDirector", "总监B");

    ProcessInstance pi = rs.startProcessInstanceByKey("businessTrip", vars);
    // 网关条件示例:${amount <= 5000} 走经理直批;${amount > 5000} 走总监审批
}

配合 1.2 的表达式分配,两个审批节点的 assignee 分别绑定 assigneeManager / assigneeDirector,流程自动路由且动态指定负责人。

三、多实例任务(会签)

现实常有「多人同时审批,全部/任一通过才算完成」的场景,这叫多实例(multi-instance) 。在 BPMN 里通过 flowable:collection + flowable:elementVariable 配置:

xml 复制代码
<userTask id="signTask" name="会签" flowable:assignee="${assignee}">
  <multiInstanceLoopCharacteristics isSequential="false"
      flowable:collection="${assigneeList}"
      flowable:elementVariable="assignee">
    <completionCondition>${nrOfCompletedInstances == nrOfInstances}</completionCondition>
  </multiInstanceLoopCharacteristics>
</userTask>
  • isSequential="false":并行会签(同时发给所有人);
  • completionConditionnrOfCompletedInstances == nrOfInstances 表示「全部完成才结束」(全票通过);改成 >= 1 就是「一票通过」。

四、网关策略进阶

网关 语义 典型用法
排他网关(XOR) 只走一条满足条件的分支 金额判断、状态分流
并行网关(AND) 同时走所有分支,汇聚时等全部 多系统并行通知
包容网关(OR) 所有满足条件的分支 多条件组合

排他网关的条件用 <conditionExpression> 写 UEL;并行网关不需要条件,汇聚节点会等所有进入的分支都到达才继续。

五、历史查询与运维

流程跑完的数据沉淀在 ACT_HI_*(历史表),通过 HistoryService 查询:

java 复制代码
List<HistoricProcessInstance> list = historyService.createHistoricProcessInstanceQuery()
        .processDefinitionKey("holidayRequest")
        .finished()                       // 只查已结束
        .orderByProcessInstanceEndTime().desc()
        .list();
for (HistoricProcessInstance hpi : list) {
    System.out.println(hpi.getStartUserId() + " 耗时:" +
        (hpi.getEndTime().getTime() - hpi.getStartTime().getTime()) + "ms");
}

常见历史查询对象:HistoricActivityInstance(节点级)、HistoricTaskInstance(任务级)、HistoricVariableInstance(变量级)。做「流程效率分析」「超时预警」都靠它们。

六、事务与异常处理

Flowable 的命令默认在一个数据库事务内执行。自定义 JavaDelegate(服务任务)里若抛异常,引擎会回滚当前事务,流程停在出错节点,可通过重试机制恢复:

java 复制代码
public class CallExternalSystemDelegate implements JavaDelegate {
    @Override
    public void execute(DelegateExecution execution) {
        // 调用外部系统;失败抛异常 → 流程事务回滚,节点不前进
        externalClient.notify(execution.getVariable("employee").toString());
    }
}

对于「外部系统偶发超时」类问题,建议在 delegate 内部做幂等重试,或结合边界事件(Boundary Event)+ 定时器事件做补偿。

七、小结

进阶篇我们补齐了工作流真正落地所需的「动态能力」:

  • 三种任务分配:固定(简单)、UEL 表达式(灵活)、监听器(规则驱动);
  • 流程变量:全局/局部作用域划分,是流程的「血液」;
  • 多实例会签:并行/串行、全票/一票规则;
  • 三类网关:排他/并行/包容的取舍;
  • 历史查询与事务:让流程可观测、可恢复。

掌握这些,你已经能应对绝大多数企业级审批流场景。下一步可以深入事件子流程、信号/消息捕获、定时边界事件,让流程具备更强的异步与补偿能力。


参考资料

相关推荐
豆沙沙包?1 小时前
c++中引用(P7-P11)
java·c++·算法
wuminyu1 小时前
虚拟线程底层ForkJoinPool的工作窃取算法机制
java·linux·c语言·jvm·c++
掉鱼的猫2 小时前
Solon 的日志设计哲学:不只是用 SLF4J 做统一门面
java
szephyr2 小时前
腾讯云 ADP 智能体的 Skills 配置变更后没有走灰度,直接全量替换出了问题怎么定位?
java·前端·腾讯云·腾讯云adp
evans在进步2 小时前
LeetCode 200:岛屿数量——Java DFS 染色法详解
java·leetcode·深度优先
带刺的坐椅2 小时前
Solon 的日志设计哲学:不只是用 SLF4J 做统一门面
java·logback·solon·log4j2
Flittly3 小时前
【雕虫大技】Agent 动态 Skill 供应链安全加固(二):执行隔离沙箱实战
java·spring boot·spring
程序员黑豆3 小时前
Java变量详解:从入门到精通
java·前端·ai编程
karry_k3 小时前
Skill 到底是什么?从提示词、脚本到多智能体工作流的完整指南
java·人工智能·后端