概述
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 相比,具有以下特点:
- 自动支持多个命名空间前缀:variables、vars、var(三个前缀都可以使用)
- 自动注入 VariableContainer:静态方法的第一个参数会自动注入 VariableContainer,用于访问流程变量
- 简化变量名处理:支持不带引号的变量名,如 var:get(myVar) 和 var:get('myVar') 都可以
- 专门用于变量操作:适合实现变量读取、比较、判断等操作
官方实现示例
以下是 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>
注意事项
- 自定义函数的 prefix 和 localName 组合必须唯一,避免与内置函数或其他自定义函数冲突
Flowable 提供两种自定义函数基类:
- AbstractFlowableFunctionDelegate:用于普通的自定义函数,需要自定义前缀,需要实现 functionClass() 和 functionMethod()
- AbstractFlowableVariableExpressionFunction:用于变量相关的函数,自动支持 var/vars/variables 前缀,静态方法第一个参数必须是 VariableContainer
- 使用 AbstractFlowableFunctionDelegate 时:
- 实际的业务逻辑应该放在独立的工具类中
- 通过 functionClass() 返回该工具类
- 通过 functionMethod() 返回具体的方法(可使用 getSingleObjectParameterMethod() 等辅助方法)
- 工具类中的方法应该是静态方法(public static)
- 使用 AbstractFlowableVariableExpressionFunction 时:
- 静态方法的第一个参数必须是 VariableContainer
- 不需要实现 functionClass() 和 functionMethod(),父类会自动处理
- 自动支持 var、vars、variables 三个前缀
- 支持不带引号的变量名
- 使用 var:get 和 var:eq 等内置函数时,变量名可以不加引号,Flowable 会自动处理
在表达式中调用自定义函数时,使用 ${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)} 格式调用函数
合理使用自定义函数可以显著提高流程定义的可读性和可维护性,将业务逻辑与流程定义解耦,便于统一管理和测试。

Comments NOTHING