规则引擎 之 QLExpress
QLExpress 是由 阿里巴巴开源的一款轻量级、高性能的 Java 表达式引擎,用于在运行时动态解析和执行字符串形式的表达式(如 "a + b > 10 ? 'yes' : 'no'")。它广泛应用于规则引擎、风控系统、配置化逻辑、报表计算等场景。
一、核心原理
1. 整体架构
QLExpress 的执行流程分为三个阶段:
[字符串表达式]
↓ (词法分析)
[Tokens: 变量、操作符、数字、括号...]
↓ (语法分析 + AST 构建)
[抽象语法树 Abstract Syntax Tree]
↓ (解释执行 / 编译优化)
[执行结果]
- 词法分析(Lexer):将字符串拆分为 Token 流(如
+,>,if, 变量名等) - 语法分析(Parser):根据运算符优先级和语法规则构建 AST
- 执行引擎(Runner):遍历 AST,结合上下文(变量值)计算结果
✅ QLExpress 不依赖 Java 编译器(javac),纯 Java 实现,无安全风险(对比
ScriptEngine)
2. 核心特性
| 特性 | 说明 |
|---|---|
| 支持丰富语法 | 四则运算、逻辑判断、三元表达式、函数调用、数组、Map 访问等 |
| 自定义函数/操作符 | 可注册 Java 方法作为表达式函数(如 myFunc(a, b)) |
| 安全沙箱 | 默认禁止反射、文件操作等危险行为 |
| 高性能 | 支持表达式缓存、AST 复用,单表达式 QPS 可达 10w+ |
| 低侵入 | 仅需引入一个 JAR 包,无外部依赖 |
二、快速入门示例
1. 添加 Maven 依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>qlexpress</artifactId>
<version>3.4.0</version> <!-- 推荐使用最新版 -->
</dependency>
GitHub 地址:https://github.com/alibaba/QLExpress
2. 基础表达式计算
import com.ql.util.express.DefaultContext;
import com.ql.util.express.ExpressRunner;
public class BasicExample {
public static void main(String[] args) throws Exception {
ExpressRunner runner = new ExpressRunner();
DefaultContext<String, Object> context = new DefaultContext<>();
// 定义变量
context.put("a", 10);
context.put("b", 20);
// 执行表达式
Object result = runner.execute("a + b * 2", context, null, true, false);
System.out.println(result); // 输出: 50
}
}
参数说明:
execute(String express, IExpressContext context, List<String> errorList, boolean isCache, boolean isTrace)
3. 条件判断与三元运算
context.put("score", 85);
Object res = runner.execute("score >= 90 ? '优秀' : (score >= 60 ? '及格' : '不及格')", context, null, true, false);
System.out.println(res); // 输出: 及格
4. 调用自定义函数(重点!)
步骤 1:定义工具类
public class MathUtils {
public static int max(int a, int b) {
return Math.max(a, b);
}
public static boolean isEmpty(String str) {
return str == null || str.isEmpty();
}
}
步骤 2:注册函数到表达式引擎
ExpressRunner runner = new ExpressRunner();
runner.addFunctionOfClassMethod("MAX", MathUtils.class.getName(), "max", new String[]{"int","int"}, null);
runner.addFunctionOfClassMethod("IS_EMPTY", MathUtils.class.getName(), "isEmpty", new String[]{"String"}, null);
DefaultContext<String, Object> context = new DefaultContext<>();
context.put("x", 10);
context.put("y", 20);
context.put("name", "");
Object result = runner.execute("MAX(x, y) == 20 && IS_EMPTY(name)", context, null, true, false);
System.out.println(result); // true
✅ 函数名在表达式中不区分大小写(默认),但建议统一风格。
5. 操作复杂对象(POJO)
public class User {
private String name;
private int age;
// getter/setter...
}
// 使用
User user = new User();
user.setName("Alice");
user.setAge(25);
context.put("user", user);
Object res = runner.execute("user.age > 18 && user.name == 'Alice'", context, null, true, false);
System.out.println(res); // true
⚠️ 注意:QLExpress 通过反射访问字段/getter,确保字段可访问或提供 public getter。
三、高级用法
1. 自定义操作符(Operator)
// 定义新操作符 "contains"
runner.addOperatorWithAlias("包含", "com.yourpkg.ContainsOperator", null);
// 实现 ContainsOperator
public class ContainsOperator extends Operator {
@Override
public Object executeInner(Object[] list) throws Exception {
String text = (String) list[0];
String keyword = (String) list[1];
return text.contains(keyword);
}
}
// 表达式使用
runner.execute("'hello world' 包含 'world'", context, null, true, false); // true
2. 表达式脚本(多行逻辑)
String script = """
if (score >= 90) {
result = "A";
} else if (score >= 80) {
result = "B";
} else {
result = "C";
}
return result;
""";
Object res = runner.execute(script, context, null, true, false);
✅ 支持
if/else、for、while等控制结构(需启用isPrecise模式)
3. 性能优化:启用缓存
// 第一次执行会解析并缓存 AST
Object r1 = runner.execute("a + b", context, null, true, false); // isCache=true
// 后续相同表达式直接复用 AST,性能提升 10 倍+
Object r2 = runner.execute("a + b", context, null, true, false);
📌 生产环境务必开启缓存(isCache=true)
4. 安全控制(防注入)
QLExpress 默认已禁用危险操作,但可进一步限制:
ExpressRunner runner = new ExpressRunner(false, false); // 禁用宏定义、禁止方法调用白名单外的方法
runner.addFunctionWhitelist("MathUtils", "max"); // 仅允许特定方法
四、典型应用场景
| 场景 | 示例 |
|---|---|
| 风控规则 | "user.riskLevel == 'HIGH' && order.amount > 10000" |
| 动态定价 | "basePrice * (1 + discountRate) - couponValue" |
| 报表公式 | "SUM(sales) / COUNT(customers) > threshold" |
| 配置化逻辑 | 在数据库存储表达式,运行时加载执行 |
| 游戏技能效果 | "player.hp -= enemy.attack * 1.5" |
五、与同类工具对比
| 工具 | 优点 | 缺点 |
|---|---|---|
| QLExpress | 高性能、安全、支持复杂语法、阿里背书 | 社区较小 |
| Aviator | 轻量、快 | 功能较简单 |
| JEXL | Apache 项目 | 性能一般 |
| Groovy/JSR223 | 功能强大 | 启动慢、有安全风险 |
| Spring Expression (SpEL) | Spring 生态集成好 | 重量级 |
✅ QLExpress 适合高并发、低延迟的规则计算场景
六、最佳实践建议
- 表达式缓存:生产环境必须开启
isCache=true - 函数白名单:避免任意方法调用,防止安全漏洞
- 异常处理:捕获
QLException并记录原始表达式 - 避免复杂逻辑:表达式应保持简单,复杂逻辑下沉到 Java 方法
- 单元测试:对关键表达式编写测试用例
七、完整示例:风控规则引擎
public class RiskEngine {
private static final ExpressRunner RUNNER = new ExpressRunner(true, false);
static {
// 注册风控函数
RUNNER.addFunctionOfClassMethod("isHighRiskIP", RiskUtils.class.getName(), "isHighRiskIP", new String[]{"String"}, null);
}
public boolean checkRisk(String rule, Map<String, Object> params) {
try {
Object result = RUNNER.execute(rule, new DefaultContext<>(params), null, true, false);
return Boolean.TRUE.equals(result);
} catch (Exception e) {
log.error("Rule execution failed: {}", rule, e);
return false; // 安全失败
}
}
}
// 使用
Map<String, Object> params = Map.of("ip", "192.168.1.100", "amount", 15000);
boolean blocked = riskEngine.checkRisk("isHighRiskIP(ip) || amount > 10000", params);
总结
QLExpress 是一款 高性能、安全、易集成 的 Java 表达式引擎,特别适合需要动态规则计算的业务场景。通过合理使用变量、自定义函数和缓存机制,可以在保证安全的同时实现灵活的业务逻辑配置化。
🔗 官方文档:https://github.com/alibaba/QLExpress/blob/master/README_CN.md
本文来自博客园,作者:蓝迷梦,转载请注明原文链接:https://www.cnblogs.com/hewei-blogs/articles/19513079

浙公网安备 33010602011771号