Java 表达式引擎技术选型分析:SpEL 与 QLExpress
本文分析了 Java 动态业务规则场景下的表达式引擎选型。对比了 SpEL、QLExpress、AviatorScript 等主流方案,涵盖编译字节码、类型安全、控制语句、沙箱机制及性能表现。针对 Spring 集成、高安全性、高性能计算及简单规则判断等不同场景给出具体推荐。同时提供了安全风险控制策略、性能优化建议及从硬编码迁移到表达式引擎的代码示例,帮助开发者根据实际需求选择合适的技术方案。

本文分析了 Java 动态业务规则场景下的表达式引擎选型。对比了 SpEL、QLExpress、AviatorScript 等主流方案,涵盖编译字节码、类型安全、控制语句、沙箱机制及性能表现。针对 Spring 集成、高安全性、高性能计算及简单规则判断等不同场景给出具体推荐。同时提供了安全风险控制策略、性能优化建议及从硬编码迁移到表达式引擎的代码示例,帮助开发者根据实际需求选择合适的技术方案。

在许多业务系统中,我们经常遇到需要动态执行业务规则的场景:
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 规则引擎 | 规则与代码分离,支持复杂规则链 | 学习成本高,系统较重 | 复杂业务规则系统(如风控) |
| 表达式引擎 | 轻量级,学习成本低,性能好 | 功能相对简单 | 简单规则判断、动态计算 |
| 脚本语言 | 功能强大,灵活 | 安全隐患,性能较差 | 需要复杂逻辑(如游戏 AI) |
| 引擎 | 首次发布时间 | 最新版本 | GitHub Stars | 维护状态 |
|---|---|---|---|---|
| AviatorScript | 2010 | 5.3.3 | 3.8k | 活跃 |
| MVEL | 2007 | 2.5.0.Final | 634 | 活跃 |
| OGNL | 2005 | 3.4.2 | 未单独统计 | 活跃 |
| SpEL | 2009 | 6.1.x | 52.9k(Spring) | 非常活跃 |
| QLExpress | 2012 | 3.3.1 | 2.3k | 活跃 |
| JEXL | 2005 | 3.3 | - | 活跃 |
| JUEL | 2006 | 2.2.7 | 78 | 维护中 |
以下是几种常见表达式语言的特性对比表格:
| 特性 | AviatorScript | MVEL | OGNL | SpEL | QLExpress | JEXL | JUEL |
|---|---|---|---|---|---|---|---|
| 编译为字节码 | ✅ | ❌ | ❌ | ✅ | ✅ | ❌ | ❌ |
| 类型安全 | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ |
| 控制语句 | ✅ | ✅ | ❌ | ❌ | ✅ | ✅ | ❌ |
| 函数定义 | ✅ | ✅ | ❌ | ❌ | ✅ | ✅ | ❌ |
| 集合支持 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ |
| 正则表达式 | ✅ | ✅ | ❌ | ✅ | ❌ | ✅ | ❌ |
| 高精度计算 | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ | ❌ |
| 安全沙箱 | ✅ | ❌ | ❌ | ❌ | ✅ | ✅ | ❌ |
综合排名(基于 JMH 基准测试):
算术性能:
AviatorScript 安全特性:
// 1. 类白名单控制
AviatorEvaluator.setOption(Options.ALLOWED_CLASS_SET, allowedClasses);
// 2. 特性开关控制
AviatorEvaluator.getInstance().disableFeature(Feature.NewInstance);
// 3. 防止死循环
AviatorEvaluator.setOption(Options.MAX_LOOP_COUNT, 10000);
QLExpress 安全特性:
// 1. 沙箱模式
QLExpressRunStrategy.setSandBoxMode(true);
// 2. 方法黑名单
QLExpressRunStrategy.addSecurityRiskMethod(System.class, "exit");
// 3. 超时控制
runner.execute(express, context, null, true, false, 1000);
JEXL 安全特性:
// 1. 权限控制
new JexlBuilder().permissions(JexlPermissions.RESTRICTED).create();
// 2. 特性禁用
new JexlBuilder().features(new JexlFeatures().loops(false)).create();
基础算术表达式:
// Java 原生
result = 100 + price * quantity - discount;
// AviatorScript
result = 100 + price * quantity - discount; // 语法接近 Java
// MVEL
result = 100 + price * quantity - discount; // 语法接近 Java
// QLExpress
result = 100 + price * quantity - discount; // 需要类型提示
// SpEL
result = 100 + #price * #quantity - #discount; // 变量前缀
条件判断:
// AviatorScript (特有语法)
if score >= 90 "优秀" elsif score >= 60 "及格" else "不及格" end
// MVEL (类 Java 语法)
if (score >= 90) { "优秀"; } else if (score >= 60) { "及格"; } else { "不及格"; }
// QLExpress (类 Java 语法)
if (score >= 90) { return "优秀"; } else if (score >= 60) { return "及格"; } else { return "不及格"; }
依赖体积排名(从小到大):
是否需要与 Spring 深度集成?
├── 是 → 选择 SpEL
└── 否 → 对安全性要求高?
├── 是 → 选择 QLExpress 或 AviatorScript
└── 否 → 性能优先?
├── 是 → 选择 AviatorScript 或 MVEL
└── 否 → 语法简单优先?
├── 是 → 选择 JEXL 或 MVEL
└── 否 → 选择 OGNL 或 JUEL
// 通用安全最佳实践
public class ExpressionSecurityConfig {
// 1. 输入验证
public static boolean validateExpression(String expr) {
// 检查长度限制
if (expr.length() > 1000) return false;
// 检查危险关键字
String[] dangerousKeywords = {"System.exit", "Runtime.exec", "ProcessBuilder"};
for (String keyword : dangerousKeywords) {
if (expr.contains(keyword)) return false;
}
return true;
}
// 2. 执行隔离
public Object executeInSandbox(String expr, Map<String, Object> context) {
// 设置执行超时
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<Object> future = executor.submit(() -> {
return expressionEngine.execute(expr, context);
});
try {
return future.get(5, TimeUnit.SECONDS);
} catch (TimeoutException e) {
future.cancel(true);
throw new ExpressionTimeoutException("表达式执行超时");
}
}
}
// 表达式缓存策略
public class ExpressionCacheManager {
private final Cache<String, CompiledExpression> cache;
public ExpressionCacheManager() {
this.cache = CacheBuilder.newBuilder()
.maximumSize(1000)
.expireAfterAccess(10, TimeUnit.MINUTES)
.build();
}
public Object executeWithCache(String expr, Map<String, Object> context) {
CompiledExpression compiled = cache.get(expr, () -> {
return expressionEngine.compile(expr);
});
return compiled.execute(context);
}
}
// 改造前
public class PriceCalculator {
public BigDecimal calculatePrice(Order order) {
BigDecimal price = order.getBasePrice();
// 硬编码的业务规则
if (order.getCustomer().getLevel() >= 3) {
price = price.multiply(new BigDecimal("0.8")); // VIP 8 折
}
if (order.getQuantity() > 10) {
price = price.multiply(new BigDecimal("0.95")); // 批量 95 折
}
return price;
}
}
// 改造后
public class ExpressionPriceCalculator {
private final ExpressionEngine engine;
private final String priceRule = "basePrice * " +
"(customer.level >= 3 ? 0.8 : 1) * " +
"(quantity > 10 ? 0.95 : 1)";
public BigDecimal calculatePrice(Order order) {
Map<String, Object> context = new HashMap<>();
context.put("basePrice", order.getBasePrice());
context.put("customer", order.getCustomer());
context.put("quantity", order.getQuantity());
return (BigDecimal) engine.execute(priceRule, context);
}
}
对于大多数 Java 项目,SpEL 是最佳选择,特别是已经使用 Spring 框架的项目。对安全性要求极高的场景,推荐 QLExpress,其沙箱机制最为完善。需要高性能计算且语法灵活的,AviatorScript 表现最优。简单规则场景且希望低学习成本的,MVEL 或 JEXL 更适合。

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online