内容纲要
序言:规则引擎
为什么我们需要规则引擎?
在现代软件开发中,我们越来越频繁地遇到这样的困境:业务规则变更的速度已经远远超过了代码发布的速度。当营销部门临时调整促销策略、风控团队紧急更新审核规则、运营人员频繁修改活动条件时,传统"修改代码→重新部署"的模式显得笨拙而低效。这正是规则引擎技术崛起的根本原因。
规则引擎通过将业务决策逻辑从应用程序代码中分离,实现了以下关键价值:
- 业务敏捷性:非技术人员可通过配置方式修改业务规则
- 降低风险:规则变更无需全量发布,减少系统稳定性风险
- 集中管理:所有业务规则可视化、可追溯、可复用
- 性能优化:专业引擎提供高效的规则匹配和执行能力
主流规则引擎全景图
目前Java生态中主流的规则引擎可分为以下几类:
引擎 | 特点 | 典型场景 |
---|---|---|
Drools | 完整的规则管理系统,支持复杂的规则流和决策表 | 金融风控、保险核保等复杂业务规则 |
QLExpress | 阿里开源,高性能轻量级,语法类似Java | 电商促销、简单风控等高频场景 |
EasyRules | 极简规则引擎,适合简单规则组合 | 小型业务规则组合 |
LiteFlow | 流程编排与任务调度 | 订单处理、业务流程自动化 |
为什么从QLExpress开始?
在规则引擎这个系列的博客中,我会探索主流的规则引擎的使用方式,并以实战的方式展现。选择QLExpress作为系列起点,是因为它完美平衡了学习曲线与实用价值:
- 对于初学者:语法直观,30分钟即可上手
- 对于架构师:性能指标足以支撑高并发场景
- 对于技术管理者:已被阿里、蚂蚁等企业验证过稳定性
一. 简介
QLExpress 是一款由阿里开源的轻量级规则引擎和动态脚本框架,专为 Java 平台设计。它允许开发者将业务规则从代码中分离出来,实现动态配置和灵活变更,解决了业务规则频繁变更的核心痛点。
核心特性
- 高性能:解释执行速度是 Groovy 的 3-5 倍
- 语法丰富:支持大部分 Java 语法和运算符
- 易于集成:与 Spring 等框架无缝整合
应用场景
- 业务规则动态化:如促销规则、风控规则等频繁变更的场景
- 公式计算:财务计算、保险保费计算等复杂公式
- 决策流程:审批流程、工单路由等条件判断
- 数据过滤:动态的数据筛选和处理逻辑
定位
场景类型 | 适用度 | 说明 |
---|---|---|
高频计算 | ★★★★★ | 如实时价格计算、优惠券核销等毫秒级响应场景 |
简单规则 | ★★★★☆ | if-else条件分支为主的业务逻辑 |
复杂规则流 | ★★☆☆☆ | 需要规则编排和复杂工作流的场景建议使用Drools |
短期活动 | ★★★★★ | 需要快速上线和下线的营销活动规则 |
二. 实战
-
添加依赖
dependencies { implementation 'com.alibaba:QLExpress:3.3.0' implementation 'org.springframework.boot:spring-boot-starter-web' }
-
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); } } -
简单规则
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); }
-
使用别名
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); } -
复杂规则+别名使用
//实现一个复杂计算规则:新用户折扣+用户等级折扣+优惠券 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); }
三. 最佳实践
- 规则版本管理:将规则存入数据库并记录版本号
- 异常处理:统一捕获
QLException
并转换为业务异常 - 日志记录:记录规则执行情况和性能指标
- 单元测试:为重要规则编写测试用例
- 语法校验:执行前先校验语法正确性
// 语法校验示例
try {
runner.parse(expression, null);
} catch (QLException e) {
log.error("规则语法错误: {}", e.getMessage());
}
四. 总结
QLExpress 是 Java 生态中规则引擎的轻量级解决方案,特别适合需要高性能、安全执行动态规则的场景。虽然它在语法灵活性上不如 Groovy 等完整脚本语言,但其卓越的性能和安全性使其成为许多企业级应用的理想选择。
通过与 SpringBoot 的集成,开发者可以快速构建出灵活、高效的规则引擎服务,实现业务逻辑的动态配置和管理。对于需要频繁变更业务规则的项目,QLExpress 无疑是一个值得考虑的解决方案。
而且比起Drools等重量级规则引擎,作为轻量级规则引擎的QLExpress语法更加简单,上手成本低。对于较为简单、需要快速开发的业务场景,QLExpress是最好的选择。
留言