规则引擎 之 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/elseforwhile 等控制结构(需启用 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 适合高并发、低延迟的规则计算场景


六、最佳实践建议

  1. 表达式缓存:生产环境必须开启 isCache=true
  2. 函数白名单:避免任意方法调用,防止安全漏洞
  3. 异常处理:捕获 QLException 并记录原始表达式
  4. 避免复杂逻辑:表达式应保持简单,复杂逻辑下沉到 Java 方法
  5. 单元测试:对关键表达式编写测试用例

七、完整示例:风控规则引擎

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

posted @ 2026-01-21 17:14  蓝迷梦  阅读(3)  评论(0)    收藏  举报