业务规则总是变,代码总是改
每个长期运行的业务系统都会面对这种场景:
- 风控规则:“新用户首次下单超过 1000 元 → 拦截”——一周后调整阈值,要发版
- 优惠券规则:“订单满 500 减 50,且非周末”——节假日要变,要发版
- 积分规则:“VIP 用户购买电子产品 3 倍积分”——业务搞活动一变就要发版
- 审批规则:“金额 > 10 万走总监审批”——金额线一调就要发版
这些规则的共同特征是——规则本身经常变化,但发版周期至少几天。把规则硬编码进代码导致两个难题:
- 业务跟不上:规则等代码发版才生效
- 代码越改越烂:if-else 嵌十几层,谁也不敢动
社区有两个 Java 生态的常用工具:
- Drools:重型规则引擎,DSL 强、推理能力强、有规则库
- Aviator:轻量级表达式引擎,类似"加强版 SpEL",编译执行速度极快
它们解决的问题在不同量级。本文讲清楚两者的定位、典型用法、踩坑点。
一、什么时候该用规则引擎
先把"该不该用"讲清楚。规则引擎不是银弹,盲目引入反而是负担:
✅ 适合用规则引擎:
- 规则数量多(几十到几百条)
- 规则经常变(业务/运营频繁调整)
- 规则需要业务/运营/产品自己改(不希望走开发流程)
- 规则之间有交叉(一条规则匹配影响其他规则)
❌ 不适合:
- 规则就 3-5 条,全年改不到一次
- 规则只能由开发改(那写代码就行)
- 性能极敏感(规则引擎再快也比硬编码慢)
- 团队没人懂规则引擎(运维/排查反而更难)
不要"为了用规则引擎而用"——多数中小项目用枚举 + 策略模式 + 配置中心就够了。
二、Aviator:轻量表达式引擎
它是什么
Aviator 是阿里开源的轻量级表达式引擎——本质是字符串表达式 + 上下文变量 → 求值:
1
2
3
4
5
6
7
8
9
| import com.googlecode.aviator.AviatorEvaluator;
Map<String, Object> ctx = new HashMap<>();
ctx.put("amount", 1500);
ctx.put("vip", true);
Boolean ok = (Boolean) AviatorEvaluator.execute(
"amount > 1000 && vip == true", ctx);
// ok = true
|
典型用法 1:动态阈值
1
2
3
4
5
6
7
8
9
10
11
12
13
| // 规则配置在数据库 / Nacos
String expr = configService.get("risk.threshold.expr");
// "amount > 1000 || (newUser && amount > 500)"
Map<String, Object> ctx = Map.of(
"amount", order.getAmount(),
"newUser", user.isNew(),
"vip", user.isVip()
);
if ((Boolean) AviatorEvaluator.execute(expr, ctx)) {
throw new BusinessException("风控拦截");
}
|
阈值改动只改配置中心,不发版。
典型用法 2:动态计算公式
1
2
3
4
5
6
7
8
9
10
| // "积分 = 金额 * 倍率 + 固定值"
String expr = "amount * rate + bonus";
Map<String, Object> ctx = Map.of(
"amount", order.getAmount(),
"rate", 0.1,
"bonus", 10
);
Long points = ((Number) AviatorEvaluator.execute(expr, ctx)).longValue();
|
Aviator 的核心特性
- 语法接近 Java——
+ - * / && || == != 都按 Java 语义 - 支持自定义函数
1
2
3
4
5
6
7
8
9
| AviatorEvaluator.addFunction(new AbstractFunction() {
public String getName() { return "isWeekend"; }
public AviatorObject call(Map<String, Object> env, AviatorObject d) {
LocalDate date = LocalDate.parse(((String) d.getValue(env)));
return AviatorBoolean.valueOf(date.getDayOfWeek().getValue() >= 6);
}
});
AviatorEvaluator.execute("isWeekend('2020-08-08')"); // true
|
1
2
| Expression compiled = AviatorEvaluator.compile("amount > 1000 && vip");
compiled.execute(ctx); // 反复调用更快
|
Aviator 的限制
- 没有"规则集"概念——你需要自己管理多条规则
- 没有 then 部分——只能算出布尔值或数值,没有"满足条件做什么"
- 不能推理——只是算单个表达式
Aviator 的本质是『把 if 条件部分抽出来变成字符串』——其余还是你自己写。
三、Drools:完整规则引擎
它是什么
Drools 是一套完整的规则引擎——基于 Rete 算法,把规则编译成决策图,能高效匹配大量规则。
它有自己的 DSL(DRL 文件):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| package com.example.rules;
import com.example.Order;
rule "新用户首单大额拦截"
when
$o: Order(amount > 1000, user.newUser == true)
then
$o.reject("新用户首单不能超过 1000");
end
rule "VIP 用户折扣"
when
$o: Order(user.vip == true, amount > 500)
then
$o.applyDiscount(0.9);
end
|
DRL 文件加载到 Drools 的 KieSession 里:
1
2
3
4
5
6
7
| KieServices ks = KieServices.Factory.get();
KieContainer kc = ks.getKieClasspathContainer();
KieSession session = kc.newKieSession();
session.insert(order);
session.fireAllRules();
session.dispose();
|
fireAllRules() 会根据规则匹配自动执行所有命中的规则——这就是规则引擎的核心。
Drools 比 Aviator 强在哪
| Aviator | Drools |
|---|
| 单条表达式 | ✓ | ✓(更复杂) |
| 规则集合 | ✗ | ✓ |
| 条件 → 动作 | ✗ | ✓ when/then |
| 规则间推理 | ✗ | ✓(Rete 算法) |
| 优先级 | ✗ | ✓ salience |
| 规则库管理 | ✗ | ✓ KieBase |
| 业务可维护性 | △ 程序员 | ✓ Drools Workbench / 工具 |
| 学习成本 | 低 | 高 |
Drools 的典型场景
- 复杂风控:100+ 条规则,规则之间有依赖
- 保险/金融:精算规则、产品定价规则
- 审批引擎:审批节点、条件分支
- 促销引擎:满减、买赠、阶梯优惠组合
Drools 的痛点
- 学习曲线陡——DRL 语法、KieSession、KieBase、AgendaGroup 等概念多
- 运维复杂——规则更新机制、Workbench 部署、版本管理
- 性能不一定快——Rete 算法在小规模规则下未必比 if-else 快
- 调试困难——规则匹配链条难追踪
不是所有"规则多变"的业务都需要 Drools。80% 的业务场景,Aviator + 配置中心就够了。
四、实战对比:同一个场景两种解法
需求:“订单金额满足条件时给折扣,规则可热更新”。
方案 A:Aviator + 配置中心
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| @Component
public class DiscountService {
@Autowired private ConfigService configService;
private Map<String, Expression> compiled = new ConcurrentHashMap<>();
public BigDecimal calculate(Order order) {
List<DiscountRule> rules = configService.getDiscountRules();
for (DiscountRule rule : rules) {
Expression expr = compiled.computeIfAbsent(
rule.getId(),
k -> AviatorEvaluator.compile(rule.getCondition()));
Map<String, Object> ctx = buildContext(order);
if ((Boolean) expr.execute(ctx)) {
return order.getAmount().multiply(rule.getRate());
}
}
return order.getAmount();
}
}
|
DiscountRule 配置:
1
2
3
4
5
6
| - id: vip_discount
condition: "vip == true && amount > 500"
rate: 0.9
- id: first_order
condition: "newUser == true"
rate: 0.95
|
优点:实现简单、学习成本极低、规则用 YAML / DB 随便配。
缺点:规则之间没有自动推理,必须按顺序匹配(开发自己 for 循环)。
方案 B:Drools
discount.drl:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| rule "VIP 折扣"
salience 100
when
$o: Order(user.vip == true, amount > 500)
then
$o.setDiscountRate(0.9);
end
rule "首单折扣"
salience 50
when
$o: Order(user.newUser == true)
then
$o.setDiscountRate(0.95);
end
|
salience 控制优先级——VIP 规则先于首单规则。
优点:规则之间能自动推理、能并行命中、可视化工具。
缺点:上手成本高、运维复杂、调试难。
五、选型决策
flowchart TD
Start([需要把规则抽出来?])
Start --> Count{规则有多少?}
Count -->|< 30 条| Simple{业务/运营要自己改?}
Count -->|>= 30 条且复杂| Complex{规则间有推理?}
Simple -->|是| Aviator[Aviator + 配置中心]
Simple -->|否| Code[直接代码 + 策略模式]
Complex -->|有| Drools[Drools]
Complex -->|没有| Aviator2[Aviator + 配置中心]给一个直接的工程建议:
轻量场景优先 Aviator + 配置中心;只有规则数量上百、规则间有推理、且团队有人专门维护时,才值得引入 Drools 这种完整框架。
六、踩坑提醒
Aviator 篇
1. 表达式注入风险:用户输入直接执行表达式可能被构造恶意代码:
1
2
3
| // ❌ 危险
String expr = userInput;
AviatorEvaluator.execute(expr);
|
要么白名单校验,要么把表达式只配在管理后台(运维/产品才能改)。
2. 类型转换:JSON 反序列化的 Number 在 Aviator 里可能是 Long 或 Double,比较时要小心:
1
2
3
| ctx.put("amount", 100L);
AviatorEvaluator.execute("amount > 50.5", ctx); // ✓
AviatorEvaluator.execute("amount > 50", ctx); // ✓
|
3. null 安全:Aviator 默认对 null 比较会报错——要在表达式里 nil != amount && amount > 100。
4. 编译缓存:同一个表达式重复 execute 时一定要预编译,否则每次都重新解析。
Drools 篇
1. 内存里的规则:DRL 改了不会自动重载——要主动 reload KieBase。生产实践通常用 Drools Workbench 或自己实现热更新。
2. then 部分别写复杂逻辑:then 是 Java 代码,但塞太多业务逻辑会让规则文件膨胀。复杂动作应该放回 Service,then 只调一行。
3. fireAllRules 的副作用:规则的 then 段可能修改 fact,触发其他规则——容易陷入"规则套规则"的循环。用 no-loop true 关键字防止。
4. 性能:Rete 算法在规则少时反而慢——50 条以下用 Drools 收益不明显。
七、规则的版本与审计
不论用哪种引擎,生产规则都需要版本管理 + 审计:
- 每条规则有"最后修改人"、“修改时间”、“生效时间”
- 修改规则要走审批(运营 → 主管 → 上线)
- 上线前在测试环境验证(Dry Run)
- 生产环境支持快速回滚到上一版本
把这些做对,比规则引擎选什么都重要。
小结
把全文压一句:
业务规则要从代码里抽出来——简单场景 Aviator 一行表达式 + 配置中心,复杂决策 Drools 完整规则引擎,绝不要硬编码 if-else 然后频繁发版。
工程实践:
- 规则数量少 + 改动频率低 → 直接策略模式 + 配置
- 规则数量中等 + 业务运营要改 → Aviator
- 规则数量大 + 规则间有交叉 → Drools
- 规则一定要版本管理 + 审计——比引擎选型更重要
把规则与代码解耦,业务才能跑得动。