1. 表达式计算工具类深度解析
在Java开发中,表达式计算是一个常见需求,特别是在工作流引擎、规则引擎等场景中。今天我要分享的是两个实用的表达式计算工具类实现:基于Flowable引擎的ValueExpressionUtil和基于Hutool工具包的ValueExpressionUtil。这两个工具类都专注于布尔表达式的计算,但在实现细节和适用场景上各有特点。
1.1 为什么需要表达式计算工具
表达式计算在业务系统中应用广泛,比如:
- 工作流引擎中的条件分支判断
- 动态规则配置和执行
- 模板化配置中的条件渲染
- 业务规则验证
手动解析表达式不仅复杂,而且容易出错。一个好的表达式计算工具应该具备:
- 支持变量替换
- 完善的错误处理机制
- 灵活的返回值处理
- 良好的性能表现
2. Flowable表达式计算工具详解
2.1 核心实现解析
Flowable版本的ValueExpressionUtil基于EL表达式引擎实现,核心是eval()方法:
java复制public static Object eval(String expression, Map<String, Object> variables) {
// 1. 创建表达式工厂
ExpressionFactory factory = new ExpressionFactoryImpl();
// 2. 创建上下文并绑定变量
SimpleContext context = new SimpleContext();
if (variables != null) {
for (Map.Entry<String, Object> entry : variables.entrySet()) {
String k = entry.getKey();
Object v = entry.getValue();
if (k == null) continue;
context.setVariable(k,
v == null ?
factory.createValueExpression(null, Object.class) :
factory.createValueExpression(v, v.getClass()));
}
}
// 3. 解析表达式
ValueExpression valueExpression = factory.createValueExpression(
context,
expression,
Object.class
);
// 4. 获取结果
return valueExpression.getValue(context);
}
关键点说明:
- 使用
ExpressionFactoryImpl创建表达式工厂 - 通过
SimpleContext绑定变量,支持null值处理 - 表达式解析时指定返回类型为Object.class,支持多种返回值
- 变量绑定时会自动处理变量类型
2.2 布尔值计算封装
工具类提供了多种便捷方法处理布尔结果:
java复制public static Boolean getBooleanValue(String expression, Map<String, Object> variables) {
return getResultBoolean(expression, variables).getData();
}
public static ResultVo<Boolean> getResultBoolean(String expression, Map<String, Object> variables) {
try {
expression = expression.trim();
Object eval = eval(expression, variables);
if (eval instanceof Boolean) {
return ResultVo.succeed("成功", (Boolean) eval);
} else if (eval instanceof Object[]) {
Object first = ((Object[]) eval)[0];
if (first instanceof Boolean) {
return ResultVo.succeed("成功", (Boolean) first);
}
return ResultVo.succeed("非Boolean类型", null);
}
return ResultVo.succeed("未知类型", null);
} catch (PropertyNotFoundException e) {
return ResultVo.failed("变量不存在: " + e.getMessage());
} catch (TreeBuilderException e) {
return ResultVo.failed("表达式错误: " + e.getMessage());
} catch (Throwable e) {
e.printStackTrace();
return ResultVo.failed("计算异常");
}
}
注意:这里使用了ResultVo封装返回结果,包含状态码、消息和数据,比直接返回布尔值提供更多上下文信息。
2.3 使用示例与测试案例
工具类提供了丰富的测试案例,覆盖各种场景:
java复制public static void main(String[] args) {
Map<String, Object> variables = new HashMap<>();
variables.put("projectMoney", 5000);
variables.put("aa", null);
// 正常案例 true
String expression1 = "${projectMoney >= 600}";
System.out.println(getBooleanValue(expression1, variables)); // true
// 正常案例 false
String expression2 = "${projectMoney >= 6000}";
System.out.println(getBooleanValue(expression2, variables)); // false
// 访问不存在的变量
String expression3 = "${contractMoney >= 1111000}";
System.out.println(getBooleanValue(expression3, variables)); // null
// 表达式错误
String expression4 = "${projectMoney >=>=> 1111000}";
System.out.println(getBooleanValue(expression4, variables)); // null
// null值判断
String expression5 = "${aa == null}";
System.out.println(getBooleanValue(expression5, variables)); // true
}
3. Hutool表达式计算工具详解
3.1 基于MVEL的实现
Hutool版本的ValueExpressionUtil基于MVEL表达式引擎,通过Hutool的ExpressionUtil封装:
java复制public static Result<Boolean> getResultBoolean(String expression, Map<String, Object> variables) {
try {
expression = expression.trim();
expression = URLUtil.decode(expression);
if (expression.startsWith("$")) {
expression = expression.substring(1);
}
Object eval = ExpressionUtil.eval(expression, variables);
if (eval instanceof Boolean) {
return Result.succeed((Boolean) eval, "成功");
} else if (eval instanceof Object[]) {
Object first = ((Object[]) eval)[0];
if (first instanceof Boolean) {
return Result.succeed((Boolean) first, "成功");
}
return Result.succeed(null, "非Boolean类型");
}
return Result.succeed(null, "未知类型");
} catch (PropertyAccessException e) {
return Result.failed(null, "变量不存在: " + e.getMessage());
} catch (CompileException e) {
return Result.failed(null, "表达式错误: " + e.getMessage());
} catch (Throwable e) {
log.error("计算异常", e);
return Result.failed(null, "系统异常");
}
}
与Flowable版本的主要区别:
- 使用MVEL而非EL表达式引擎
- 自动处理URL解码
- 支持去除表达式开头的$符号
- 使用Hutool自带的Result封装类
3.2 性能对比与选择建议
在实际项目中如何选择:
| 特性 | Flowable版本 | Hutool版本 |
|---|---|---|
| 表达式引擎 | EL表达式 | MVEL |
| 依赖 | Flowable引擎 | Hutool工具包 |
| 性能 | 中等 | 较高 |
| 功能丰富度 | 基础 | 丰富 |
| 学习曲线 | 较陡 | 平缓 |
| 适用场景 | 工作流相关 | 通用业务 |
建议:
- 如果项目已使用Flowable,优先使用其表达式引擎保持一致性
- 如果是通用业务场景,Hutool版本更轻量且功能丰富
- 对性能要求高的场景可以测试两者实际表现
4. 高级用法与实战技巧
4.1 表达式语法扩展
两种工具都支持常见的表达式语法:
- 算术运算:
${a + b * c} - 比较运算:
${x > 10 && y < 20} - 逻辑运算:
${flag1 || flag2} - 三元运算:
${condition ? value1 : value2} - 方法调用:
${obj.method(param)}
提示:Flowable版本使用EL表达式语法,变量引用需要${}包裹;Hutool版本使用MVEL语法,默认不需要${}。
4.2 性能优化建议
- 表达式预编译:对于频繁使用的表达式,考虑预编译
java复制// Flowable版本预编译示例
ValueExpression precompiledExpr = factory.createValueExpression(
context, expression, Object.class);
// 后续可重复使用precompiledExpr
-
变量缓存:如果变量集合变化不大,可以缓存上下文对象
-
异常处理优化:避免在循环中频繁处理异常,可以提前校验表达式
-
线程安全:注意ExpressionFactory的线程安全性(通常线程安全)
4.3 常见问题排查
-
变量未找到错误
- 检查变量名拼写
- 确认变量是否真的放入variables Map
- 检查变量是否为null
-
表达式语法错误
- 检查括号是否匹配
- 确认运算符使用正确
- 复杂表达式建议拆解测试
-
类型转换问题
- 明确变量类型,避免隐式转换
- 对于可能为null的值做特殊处理
-
性能问题
- 避免在循环中重复创建ExpressionFactory
- 考虑使用更简单的表达式
- 对频繁调用的表达式进行预编译
5. 设计模式与最佳实践
5.1 工具类设计思考
这两个工具类都体现了良好的设计原则:
- 单一职责原则:专注于表达式计算
- 开闭原则:通过扩展支持不同表达式引擎
- 实用主义:提供便捷方法和详细方法两种接口
- 错误隔离:捕获所有异常,避免影响主流程
5.2 扩展建议
根据项目需求,可以考虑扩展:
- 支持更多表达式引擎(SpEL、OGNL等)
- 添加表达式验证功能
- 支持表达式模板(如Hello ${name})
- 添加性能监控统计
- 支持表达式注释
5.3 单元测试策略
完善的单元测试应该覆盖:
- 正常功能测试
- 边界条件测试
- 异常场景测试
- 性能基准测试
- 并发安全测试
测试示例:
java复制@Test
public void testGetBooleanValue() {
Map<String, Object> vars = MapUtil.builder("a", 10).put("b", 20).build();
// 正常情况
assertTrue(ValueExpressionUtil.getBooleanValue("a < b", vars));
// 变量不存在
assertNull(ValueExpressionUtil.getBooleanValue("c > 0", vars));
// 表达式错误
assertNull(ValueExpressionUtil.getBooleanValue("a > > b", vars));
// null值处理
vars.put("c", null);
assertTrue(ValueExpressionUtil.getBooleanValue("c == null", vars));
}
在实际项目中,表达式计算工具的选择和实现需要根据具体需求和技术栈来决定。这两个实现方案都经过了生产环境验证,可以直接使用或作为参考实现。关键是根据项目特点进行必要的调整和扩展,特别是在性能和安全方面需要额外关注。