内容纲要

序言:规则引擎

为什么我们需要规则引擎?

在现代软件开发中,我们越来越频繁地遇到这样的困境:业务规则变更的速度已经远远超过了代码发布的速度。当营销部门临时调整促销策略、风控团队紧急更新审核规则、运营人员频繁修改活动条件时,传统"修改代码→重新部署"的模式显得笨拙而低效。这正是规则引擎技术崛起的根本原因。

规则引擎通过将业务决策逻辑从应用程序代码中分离,实现了以下关键价值:

  1. 业务敏捷性:非技术人员可通过配置方式修改业务规则
  2. 降低风险:规则变更无需全量发布,减少系统稳定性风险
  3. 集中管理:所有业务规则可视化、可追溯、可复用
  4. 性能优化:专业引擎提供高效的规则匹配和执行能力

主流规则引擎全景图

目前Java生态中主流的规则引擎可分为以下几类:

引擎 特点 典型场景
Drools 完整的规则管理系统,支持复杂的规则流和决策表 金融风控、保险核保等复杂业务规则
QLExpress 阿里开源,高性能轻量级,语法类似Java 电商促销、简单风控等高频场景
EasyRules 极简规则引擎,适合简单规则组合 小型业务规则组合
LiteFlow 流程编排与任务调度 订单处理、业务流程自动化

为什么从QLExpress开始?

规则引擎这个系列的博客中,我会探索主流的规则引擎的使用方式,并以实战的方式展现。选择QLExpress作为系列起点,是因为它完美平衡了学习曲线实用价值

  • 对于初学者:语法直观,30分钟即可上手
  • 对于架构师:性能指标足以支撑高并发场景
  • 对于技术管理者:已被阿里、蚂蚁等企业验证过稳定性

一. 简介

QLExpress 是一款由阿里开源的轻量级规则引擎和动态脚本框架,专为 Java 平台设计。它允许开发者将业务规则从代码中分离出来,实现动态配置和灵活变更,解决了业务规则频繁变更的核心痛点。

核心特性

  • 高性能:解释执行速度是 Groovy 的 3-5 倍
  • 语法丰富:支持大部分 Java 语法和运算符
  • 易于集成:与 Spring 等框架无缝整合

应用场景

  1. 业务规则动态化:如促销规则、风控规则等频繁变更的场景
  2. 公式计算:财务计算、保险保费计算等复杂公式
  3. 决策流程:审批流程、工单路由等条件判断
  4. 数据过滤:动态的数据筛选和处理逻辑

定位

场景类型 适用度 说明
高频计算 ★★★★★ 如实时价格计算、优惠券核销等毫秒级响应场景
简单规则 ★★★★☆ if-else条件分支为主的业务逻辑
复杂规则流 ★★☆☆☆ 需要规则编排和复杂工作流的场景建议使用Drools
短期活动 ★★★★★ 需要快速上线和下线的营销活动规则

二. 实战

  1. 添加依赖

    dependencies {
       implementation 'com.alibaba:QLExpress:3.3.0'
       implementation 'org.springframework.boot:spring-boot-starter-web'
    }
  2. QLExpress工具类

    public class ExpressRunnerHolder {
    
       private static final ExpressRunner EXPRESS_RUNNER = new ExpressRunner();
    
       public static ExpressRunner getInstance() {
           return EXPRESS_RUNNER;
       }
    
       public static Object execute(String express, Map contextMap) throws Exception {
           IExpressContext context = new DefaultContext();
           if (contextMap != null) {
               contextMap.forEach(context::put);
           }
           return execute(express, context);
       }
    
       private static Object execute(String express, IExpressContext context) throws Exception {
           return getInstance().execute(express, context, null, true, false);
       }
    }
  3. 简单规则

    public String executeSimpleRule(int age, boolean vip) {
       // 构建 if-else 规则
       String express = "if (0 < age && age < 18) {\n" +
               "    return \"青少年专享\";\n" +
               "} else if (age >= 60 && vip) {\n" +
               "    return \"银发VIP特权\";\n" +
               "} else if (vip) {\n" +
               "    return \"VIP会员服务\";\n" +
               "} else {\n" +
               "    return \"普通服务\";\n" +
               "}";
    
       // 构建上下文
       Map context = new HashMap<>();
       context.put("age", age);
       context.put("vip", vip);
    
       // 执行规则
       Object result = ExpressRunnerHolder.execute(express, context);
       return result != null ? result.toString() : "执行结果为空";
    }

    测试

    @Test
    void testExecuteSimpleRule_UnderAge() {
       // 准备测试数据 - 17岁非VIP
       int age = 17;
       boolean vip = false;
    
       // 执行测试
       String result = ruleEngineService.executeSimpleRule(age, vip);
    
       // 验证结果
       assertEquals("青少年专享", result);
    }
  4. 使用别名

    public static Object executeWithAlias(String express, Map contextMap) {
       ExpressRunner runner = new ExpressRunner();
    
       // 定义别名,使规则表达式更简洁易读
       runner.addOperatorWithAlias("如果", "if", null);
       runner.addOperatorWithAlias("否则", "else", null);
       runner.addOperatorWithAlias("等于", "==", null);
       runner.addOperatorWithAlias("大于等于", ">=", null);
       runner.addOperatorWithAlias("小于等于", "<=", null);
       runner.addOperatorWithAlias("并且", "&&", null);
       runner.addOperatorWithAlias("或者", "||", null);
       runner.addOperatorWithAlias("小于", "<", null);
       runner.addOperatorWithAlias("大于", ">", null);
       runner.addOperatorWithAlias("不等于", "!=", null);
    
       // 创建上下文
       IExpressContext context = new DefaultContext();
       if (contextMap != null) {
           contextMap.forEach(context::put);
       }
    
       // 执行表达式
       return runner.execute(express, context, null, true, false);
    }

    测试

    @Test
    public void testExecuteWithAlias() throws Exception {
       // 准备测试数据
       String express = "if (value 大于等于 10 并且 value 小于等于 20) { return true; } else { return false; }";
       Map context = new HashMap<>();
       context.put("value", 15);
    
       // 执行测试
       Object result = ExpressRunnerHolder.executeWithAlias(express, context);
    
       // 验证结果
       assertEquals(true, result);
    }
  5. 复杂规则+别名使用

    //实现一个复杂计算规则:新用户折扣+用户等级折扣+优惠券
    public double executeComplexRuleWithAlias(double orderAmount, int userLevel, boolean isNewUser, boolean hasCoupon) throws Exception {
       String express = "discount = 1.0;\n" +
               "if (isNewUser) {\n" +
               "    discount = discount * 0.9;\n" +
               "}\n" +
               "if (userLevel 大于等于 1 并且 userLevel 小于等于 5) {\n" +
               "    levelDiscount = 1.0 - (userLevel * 0.05);\n" +
               "    discount = discount * levelDiscount;\n" +
               "}\n" +
               "if (hasCoupon) {\n" +
               "    discount = discount * 0.95;\n" +
               "}\n" +
               "if (discount 小于 0.7) {\n" +
               "    discount = 0.7;\n" +
               "}\n" +
               "finalPrice = orderAmount * discount;\n" +
               "return Math.round(finalPrice * 100) / 100.0;";
    
       // 构建上下文
       Map context = new HashMap<>();
       context.put("orderAmount", orderAmount);
       context.put("userLevel", userLevel);
       context.put("isNewUser", isNewUser);
       context.put("hasCoupon", hasCoupon);
    
       Object result = ExpressRunnerHolder.executeWithAlias(express, context);
       return result != null ? Double.parseDouble(result.toString()) : orderAmount;
    }

    测试

    @Test
    void testExecuteComplexRuleWithAlias_CombinedDiscounts() throws Exception {
       // 准备测试数据 - 新用户,3级VIP,有优惠券
       double orderAmount = 100.0;
       int userLevel = 3;
       boolean isNewUser = true;
       boolean hasCoupon = true;
    
       // 执行测试
       double result = ruleEngineService.executeComplexRuleWithAlias(orderAmount, userLevel, isNewUser, hasCoupon);
    
       // 验证结果 - 应该是原价的 90% * 85% * 95% = 72.675%,但由于保留两位小数,应为72.68
       assertEquals(72.68, result, 0.01);
    }

三. 最佳实践

  1. 规则版本管理:将规则存入数据库并记录版本号
  2. 异常处理:统一捕获 QLException 并转换为业务异常
  3. 日志记录:记录规则执行情况和性能指标
  4. 单元测试:为重要规则编写测试用例
  5. 语法校验:执行前先校验语法正确性
// 语法校验示例
try {
    runner.parse(expression, null);
} catch (QLException e) {
    log.error("规则语法错误: {}", e.getMessage());
}

四. 总结

QLExpress 是 Java 生态中规则引擎的轻量级解决方案,特别适合需要高性能、安全执行动态规则的场景。虽然它在语法灵活性上不如 Groovy 等完整脚本语言,但其卓越的性能和安全性使其成为许多企业级应用的理想选择。

通过与 SpringBoot 的集成,开发者可以快速构建出灵活、高效的规则引擎服务,实现业务逻辑的动态配置和管理。对于需要频繁变更业务规则的项目,QLExpress 无疑是一个值得考虑的解决方案。

而且比起Drools等重量级规则引擎,作为轻量级规则引擎的QLExpress语法更加简单,上手成本低。对于较为简单、需要快速开发的业务场景,QLExpress是最好的选择。

最后修改日期: 2025年5月11日

留言

撰写回覆或留言

发布留言必须填写的电子邮件地址不会公开。