概述

Flowable 自定义函数是一种扩展流程表达式能力的机制,允许开发者在流程定义的表达式中调用自定义的 Java 方法。通过实现 FlowableFunctionDelegate 接口并注册到流程引擎,可以在 BPMN 流程的条件表达式、监听器表达式等场景中使用这些自定义函数。

自定义函数主要解决以下问题:

  • 简化复杂的表达式逻辑,提高可读性
  • 封装业务逻辑,避免在流程定义中编写冗长的表达式
  • 提供可复用的函数库,统一处理常见的业务场景
  • 优雅处理变量空值问题,避免 PropertyNotFoundException 异常

适用场景包括条件网关判断、任务分配表达式、执行监听器表达式、多实例循环条件等需要动态计算的流程节点。

使用方式

1. 实现自定义函数

自定义函数需要继承 AbstractFlowableFunctionDelegate 抽象类,并实现相关方法:

import org.flowable.common.engine.impl.el.AbstractFlowableFunctionDelegate;
import java.lang.reflect.Method;

public class FindUserFunctionDelegate extends AbstractFlowableFunctionDelegate {

    @Override
    public String prefix() {
        // 定义函数前缀,在表达式中使用时的命名空间
        return "user";
    }

    @Override
    public String localName() {
        // 定义函数名称
        return "findUser";
    }

    @Override
    public Class<?> functionClass() {
        // 返回包含实际业务逻辑的工具类
        return UserUtil.class;
    }

    @Override
    public Method functionMethod() {
        // 返回具体的方法对象
        return getSingleObjectParameterMethod();
    }
}

对应的工具类实现:

public class UserUtil {

    public static User findUser(String userId) {
        // 实现用户查询逻辑
        return userService.findById(userId);
    }
}

2. 注册自定义函数

Flowable 提供了多种方式注册自定义函数,根据项目架构选择合适的方式。

方式一:Spring Boot 方式(推荐)

在 Spring Boot 应用中,通过配置类注册自定义函数:

import org.flowable.spring.boot.FlowableFunctionDelegatesProvider;
import org.flowable.spring.boot.BaseFlowableFunctionDelegatesProvider;
import org.flowable.common.engine.api.delegate.FlowableFunctionDelegate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Arrays;
import java.util.Collection;

@Configuration
public class FlowableConfig {

    @Bean
    public FlowableFunctionDelegatesProvider myFunctionDelegateProvider() {
        return new BaseFlowableFunctionDelegatesProvider() {
            @Override
            public Collection<FlowableFunctionDelegate> getFunctionDelegates() {
                return Arrays.asList(
                    new FindUserFunctionDelegate(),
                    new DateDiffFunctionDelegate(),
                    new CustomVariableCheckFunction()
                    // 添加更多自定义函数
                );
            }
        };
    }
}

方式二:编程方式

通过 ProcessEngineConfiguration 直接设置自定义函数:

import org.flowable.engine.ProcessEngine;
import org.flowable.engine.ProcessEngineConfiguration;
import org.flowable.engine.impl.cfg.StandaloneProcessEngineConfiguration;
import java.util.Arrays;

public class FlowableEngineBuilder {

    public ProcessEngine buildProcessEngine() {
        ProcessEngineConfiguration cfg = new StandaloneProcessEngineConfiguration()
            .setJdbcUrl("jdbc:h2:mem:flowable;DB_CLOSE_DELAY=-1")
            .setJdbcUsername("sa")
            .setJdbcPassword("")
            .setJdbcDriver("org.h2.Driver")
            .setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE);

        // 设置自定义函数
        cfg.setCustomFlowableFunctionDelegates(Arrays.asList(
            new FindUserFunctionDelegate(),
            new DateDiffFunctionDelegate(),
            new CustomVariableCheckFunction()
        ));

        return cfg.buildProcessEngine();
    }
}

方式三:XML 配置方式

在 flowable.cfg.xml 配置文件中注册自定义函数:

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="processEngineConfiguration"
          class="org.flowable.engine.impl.cfg.StandaloneProcessEngineConfiguration">

        <property name="jdbcUrl" value="jdbc:h2:mem:flowable;DB_CLOSE_DELAY=-1"/>
        <property name="jdbcDriver" value="org.h2.Driver"/>
        <property name="jdbcUsername" value="sa"/>
        <property name="jdbcPassword" value=""/>
        <property name="databaseSchemaUpdate" value="true"/>

        <!-- 注册自定义函数 -->
        <property name="customFlowableFunctionDelegates">
            <list>
                <bean class="com.example.function.FindUserFunctionDelegate"/>
                <bean class="com.example.function.DateDiffFunctionDelegate"/>
                <bean class="com.example.function.CustomVariableCheckFunction"/>
            </list>
        </property>
    </bean>

    <bean id="processEngine" class="org.flowable.engine.ProcessEngineConfiguration"
          factory-bean="processEngineConfiguration"
          factory-method="buildProcessEngine"/>

</beans>

方式对比

方式 适用场景 优点 缺点
Spring Boot Spring Boot 项目 简洁、自动配置、易于管理 仅限 Spring Boot
编程方式 非 Spring 项目、需要动态配置 灵活、可编程控制 代码量较多
XML 配置 传统 Spring 项目 配置集中、易于维护 配置文件较长

3. 在流程表达式中使用

注册完成后,可以在流程定义的表达式中使用自定义函数:

<!-- 条件表达式 -->
<sequenceFlow id="flow1" sourceRef="task1" targetRef="task2">
  <conditionExpression xsi:type="tFormalExpression">
     <!-- 这里支持链式调用,因为findUser返回的是User对象 -->
    ${user:findUser(userId).getLastName() == 'Doe'}
  </conditionExpression>
</sequenceFlow>

内置变量函数 AbstractFlowableVariableExpressionFunction

Flowable 提供了一组内置的变量处理函数,这些函数继承自 AbstractFlowableVariableExpressionFunction,专门用于简化变量操作和空值处理。

AbstractFlowableVariableExpressionFunction 特点

AbstractFlowableVariableExpressionFunction 是一个特殊的抽象类,与普通的 AbstractFlowableFunctionDelegate 相比,具有以下特点:

  1. 自动支持多个命名空间前缀:variables、vars、var(三个前缀都可以使用)
  2. 自动注入 VariableContainer:静态方法的第一个参数会自动注入 VariableContainer,用于访问流程变量
  3. 简化变量名处理:支持不带引号的变量名,如 var:get(myVar) 和 var:get('myVar') 都可以
  4. 专门用于变量操作:适合实现变量读取、比较、判断等操作

官方实现示例

以下是 Flowable 官方的 var:get 和 var:eq 实现方式:

// var:get 的官方实现
public class VariableGetExpressionFunction extends AbstractFlowableVariableExpressionFunction {

    public VariableGetExpressionFunction() {
        super("get");  // 函数名
    }

    // 静态方法,第一个参数是 VariableContainer
    public static Object get(VariableContainer variableContainer, String variableName) {
        return getVariableValue(variableContainer, variableName);
    }
}

// var:eq 的官方实现
public class VariableEqualsExpressionFunction extends AbstractFlowableVariableExpressionFunction {

    public VariableEqualsExpressionFunction() {
        super(Arrays.asList("equals", "eq"), "equals");  // 支持多个函数名
    }

    // 静态方法,第一个参数是 VariableContainer
    public static boolean equals(VariableContainer variableContainer, String variableName, Object comparedValue) {
        Object variableValue = getVariableValue(variableContainer, variableName);
        // 处理空值和数字类型比较 ...
        return ...
    }
}

参数注入机制对比

两种自定义函数的参数注入机制完全不同,理解这一点对于正确使用它们至关重要。

AbstractFlowableFunctionDelegate 的参数注入

工作原理

  • 使用 JUEL(Java Unified Expression Language)表达式引擎解析表达式
  • 表达式中的变量名会从流程变量作用域中查找和解析
  • 解析后的变量值直接作为参数传递给静态方法

示例

// 表达式:${date:daysBetween(startDate, endDate) > 7}
// 执行流程:
// 1. 从流程变量中获取 startDate 的值(假设是 Date 对象)
// 2. 从流程变量中获取 endDate 的值(假设是 Date 对象)
// 3. 调用:DateUtil.daysBetween(startDateValue, endDateValue)

特点

  • 参数是已经解析好的变量值
  • 如果流程变量中不存在该变量,会抛出 PropertyNotFoundException
  • 适用于需要直接操作变量值的场景

AbstractFlowableVariableExpressionFunction 的参数注入

工作原理

  • 框架会自动在参数列表前面插入 VariableContainer 参数
  • 变量名会被转换为字符串参数(而不是解析为变量值)
  • 支持不带引号的变量名

示例

// 表达式:{var:get(userName)}
// 执行流程:
// 1. 自动注入 VariableContainer
// 2. 将 userName 转换为字符串 "userName"
// 3. 调用:VariableGetExpressionFunction.get(variableContainer, "userName")
// 4. 在函数内部通过 variableContainer.getVariable("userName") 获取值
注意:{var:equals(userName,"abc")} 这里的userName会从流程变量VariableContainer中获取,所以不管怎么写都是一个参数Key,而"abc"不同,这里带不带引号都是一个具体的值,不带引号会被解析成Number类型进行比较,带参数后通过Objects.equals()方法进行比较。
总结:AbstractFlowableVariableExpressionFunction函数会自动注入VariableContainer容器,同时会将第一个参数进行转换成带引号的Key,然后就可以从容器中获取,而对于后面的参数都是一个具体的值,不会从容器中获取,具体可以查看源码VariableEqualsExpressionFunction是怎么处理。
// 自动注入 VariableContainer 源码
// AbstractFlowableVariableExpressionFunction 会自动在参数列表前面插入 VariableContainer
// 变量名会被转换为字符串(支持不带引号)
// 其他参数保持原样
@Override
public AstFunction createFunction(String name, int index, AstParameters parameters, boolean varargs, FlowableExpressionParser parser) {
    Method method = functionMethod();
    int parametersCardinality = parameters.getCardinality();
    int methodParameterCount = method.getParameterCount();
    if (method.isVarArgs() || parametersCardinality < methodParameterCount) {
        // 创建一个新的参数列表
        List<AstNode> newParameters = new ArrayList<>(parametersCardinality + 1);
        // 第一个参数是 variableContainer 标识符
        // variableScopeName 是静态变量 
        newParameters.add(parser.createIdentifier(variableScopeName));

        if (methodParameterCount >= 1) {
            // 如果第一个参数是标识符,转换为文本节点
            // 这样可以支持不带引号的变量名
            newParameters.add(createVariableNameNode(parameters.getChild(0)));
            for (int i = 1; i < parametersCardinality; i++) {
                // 其余参数保持原样
                newParameters.add(parameters.getChild(i));
            }
        }

        return new AstFunction(name, index, new AstParameters(newParameters), varargs);
    } else {
        return new AstFunction(name, index, parameters, varargs);
    }
}

特点

  • 第一个参数自动注入 VariableContainer
  • 变量名作为字符串传递,不会提前解析
  • 可以在函数内部判断变量是否存在,避免异常
  • 适用于需要安全处理变量的场景

对比总结

特性 AbstractFlowableFunctionDelegate AbstractFlowableVariableExpressionFunction
参数来源 从流程变量中解析值 第一个参数是 VariableContainer,变量名作为字符串
变量不存在时 抛出 PropertyNotFoundException 不抛出异常(由函数内部处理)
参数注入方式 JUEL 自动解析变量值 框架自动插入 VariableContainer
使用场景 普通业务函数(用户查询、日期计算等) 变量操作函数(变量读取、比较、判断等)
命名空间前缀 需要自定义 自动支持 var/vars/variables

函数列表

1. var:get(varName)

获取变量值,当变量不存在时不会抛出异常,而是返回 null。

使用形式:

// 基本用法
{var:get(myVariable)}

// 与比较运算符结合{var:get(myVariable) == 'hello'}

// 等价写法(不需要引号)
{var:get(myVariable)}{var:get('myVariable')}
{var:get("myVariable")}

// 命名空间别名{variables:get(myVariable)}
${vars:get(myVariable)}

2. var:eq(varName, value)

判断变量是否等于指定值,自动处理变量为 null 的情况。

使用形式:

// 基本用法
{var:eq(someVariable, someValue)}

// 字符串比较{var:eq(status, 'approved')}

// 数字比较
{var:eq(count, 10)}

// 等价的完整表达式(var:eq 的简化版本){execution.getVariable('someVariable') != null && execution.getVariable('someVariable') == someValue}

// 命名空间别名
{variables:equals(someVariable, someValue)}{vars:eq(someVariable, someValue)}

使用场景示例

场景 1:条件网关判断

传统写法(容易出错):

<conditionExpression xsi:type="tFormalExpression">
  ${someVariable == someValue}
</conditionExpression>

问题:如果 someVariable 不存在,会抛出 PropertyNotFoundException。

改进写法:

<conditionExpression xsi:type="tFormalExpression">
  ${var:eq(someVariable, someValue)}
</conditionExpression>

场景 2:多实例循环条件

<multiInstanceLoopCharacteristics isSequential="false">
  <completionCondition>
    ${var:get(approvedCount) >= var:get(totalCount) * 0.6}
  </completionCondition>
</multiInstanceLoopCharacteristics>

场景 3:任务监听器表达式

<userTask id="reviewTask" name="审核任务">
  <extensionElements>
    <flowable:taskListener event="create"
      expression="${var:eq(taskType, 'urgent') ? urgentHandler : normalHandler}">
    </flowable:taskListener>
  </extensionElements>
</userTask>

注意事项

  1. 自定义函数的 prefix 和 localName 组合必须唯一,避免与内置函数或其他自定义函数冲突

  2. Flowable 提供两种自定义函数基类:

    • AbstractFlowableFunctionDelegate:用于普通的自定义函数,需要自定义前缀,需要实现 functionClass() 和 functionMethod()
    • AbstractFlowableVariableExpressionFunction:用于变量相关的函数,自动支持 var/vars/variables 前缀,静态方法第一个参数必须是 VariableContainer
  3. 使用 AbstractFlowableFunctionDelegate 时:
    • 实际的业务逻辑应该放在独立的工具类中
    • 通过 functionClass() 返回该工具类
    • 通过 functionMethod() 返回具体的方法(可使用 getSingleObjectParameterMethod() 等辅助方法)
    • 工具类中的方法应该是静态方法(public static)
  4. 使用 AbstractFlowableVariableExpressionFunction 时:
    • 静态方法的第一个参数必须是 VariableContainer
    • 不需要实现 functionClass() 和 functionMethod(),父类会自动处理
    • 自动支持 var、vars、variables 三个前缀
    • 支持不带引号的变量名
  5. 使用 var:get 和 var:eq 等内置函数时,变量名可以不加引号,Flowable 会自动处理

  6. 在表达式中调用自定义函数时,使用 ${prefix:localName(args)} 格式,冒号前是前缀,冒号后是函数名

使用示例

示例 1:用户查询函数

实现一个根据用户 ID 查询用户信息的函数:

import org.flowable.common.engine.impl.el.AbstractFlowableFunctionDelegate;
import java.lang.reflect.Method;

public class FindUserFunctionDelegate extends AbstractFlowableFunctionDelegate {

    @Override
    public String prefix() {
        return "user";
    }

    @Override
    public String localName() {
        return "findUser";
    }

    @Override
    public Class<?> functionClass() {
        return UserUtil.class;
    }

    @Override
    public Method functionMethod() {
        return getSingleObjectParameterMethod();
    }
}

对应的工具类:

public class UserUtil {

    private static UserService userService = SpringContextHolder.getBean(UserService.class);

    public static User findUser(String userId) {
        if (userId == null) {
            return null;
        }
        return userService.findById(userId);
    }
}

在流程中使用:

<conditionExpression xsi:type="tFormalExpression">
  ${user:findUser(userId).getLastName() == 'Doe'}
</conditionExpression>

示例 2:日期计算函数

实现一个计算日期差的函数:

import org.flowable.common.engine.impl.el.AbstractFlowableFunctionDelegate;
import java.lang.reflect.Method;
import java.util.Date;

public class DateDiffFunctionDelegate extends AbstractFlowableFunctionDelegate {

    @Override
    public String prefix() {
        return "date";
    }

    @Override
    public String localName() {
        return "daysBetween";
    }

    @Override
    public Class<?> functionClass() {
        return DateUtil.class;
    }

    @Override
    public Method functionMethod() {
        try {
            return DateUtil.class.getMethod("daysBetween", Date.class, Date.class);
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        }
    }
}

对应的工具类:

import java.util.Date;
import java.util.concurrent.TimeUnit;

public class DateUtil {

    public static long daysBetween(Date startDate, Date endDate) {
        if (startDate == null || endDate == null) {
            return 0;
        }
        long diff = endDate.getTime() - startDate.getTime();
        return TimeUnit.DAYS.convert(diff, TimeUnit.MILLISECONDS);
    }
}

在流程中使用:

<conditionExpression xsi:type="tFormalExpression">
  ${date:daysBetween(startDate, endDate) > 7}
</conditionExpression>

示例 3:自定义变量函数(类似 var:get)

如果需要实现类似 var:get 的自定义变量函数,应该继承 AbstractFlowableVariableExpressionFunction:

import org.flowable.common.engine.impl.el.function.AbstractFlowableVariableExpressionFunction;
import org.flowable.common.engine.api.variable.VariableContainer;
import java.util.Collection;

public class CustomVariableCheckFunction extends AbstractFlowableVariableExpressionFunction {

    public CustomVariableCheckFunction() {
        super("isNotEmpty");  // 函数名
    }

    // 静态方法,第一个参数必须是 VariableContainer
    public static boolean isNotEmpty(VariableContainer variableContainer, String variableName) {
        Object value = variableContainer.getVariable(variableName);
        if (value == null) {
            return false;
        }
        if (value instanceof String) {
            return !((String) value).isEmpty();
        }
        if (value instanceof Collection) {
            return !((Collection<?>) value).isEmpty();
        }
        return true;
    }
}

在流程中使用:

<!-- 自动支持 var、vars、variables 三个前缀 -->
<conditionExpression xsi:type="tFormalExpression">
  {var:isNotEmpty(userName)}
</conditionExpression>

<!-- 或者使用其他前缀 -->
<conditionExpression xsi:type="tFormalExpression">{variables:isNotEmpty(userName)}
</conditionExpression>

关键特点:

  • 继承 AbstractFlowableVariableExpressionFunction 后,自动支持 var、vars、variables 三个前缀
  • 静态方法的第一个参数必须是 VariableContainer
  • 不需要实现 functionClass() 和 functionMethod(),父类会自动处理
  • 支持不带引号的变量名,如 var:isNotEmpty(userName) 和 var:isNotEmpty('userName') 都可以

示例 4:结合内置函数使用

<!-- 检查变量是否存在且满足条件 -->
<conditionExpression xsi:type="tFormalExpression">
  {var:get(approvalStatus) != null && user:findUser(var:get(approverId)).getDepartment() == 'Finance'}
</conditionExpression>

<!-- 使用 var:eq 简化空值判断 -->
<conditionExpression xsi:type="tFormalExpression">{var:eq(processType, 'urgent') && var:get(priority) > 5}
</conditionExpression>

总结

Flowable 自定义函数机制提供了强大的表达式扩展能力,通过继承 AbstractFlowableFunctionDelegate 抽象类可以将复杂的业务逻辑封装为可复用的函数。内置的 AbstractFlowableVariableExpressionFunction 系列函数(如 var:get、var:eq)专门用于简化变量操作,优雅处理空值问题,避免常见的 PropertyNotFoundException 异常。

核心要点:

  • 自定义函数需要继承 AbstractFlowableFunctionDelegate 抽象类
  • 通过 prefix 和 localName 定义命名空间和函数名
  • 通过 functionClass() 返回包含业务逻辑的工具类,通过 functionMethod() 返回具体的方法
  • Spring Boot 环境下通过 FlowableFunctionDelegatesProvider 注册函数
  • 使用 var:get 和 var:eq 等内置函数可以安全处理可能不存在的变量
  • 工具类中的方法应该是静态方法,确保线程安全
  • 表达式中使用 ${prefix:functionName(args)} 格式调用函数

合理使用自定义函数可以显著提高流程定义的可读性和可维护性,将业务逻辑与流程定义解耦,便于统一管理和测试。