Featured image of post Drools 与 Aviator:Java 业务规则引擎实战

Drools 与 Aviator:Java 业务规则引擎实战

把『if-else』变成可热更新的业务规则——Drools 重决策、Aviator 轻表达式,本文讲清两者定位与典型用法

业务规则总是变,代码总是改

每个长期运行的业务系统都会面对这种场景:

  • 风控规则:“新用户首次下单超过 1000 元 → 拦截”——一周后调整阈值,要发版
  • 优惠券规则:“订单满 500 减 50,且非周末”——节假日要变,要发版
  • 积分规则:“VIP 用户购买电子产品 3 倍积分”——业务搞活动一变就要发版
  • 审批规则:“金额 > 10 万走总监审批”——金额线一调就要发版

这些规则的共同特征是——规则本身经常变化,但发版周期至少几天。把规则硬编码进代码导致两个难题:

  1. 业务跟不上:规则等代码发版才生效
  2. 代码越改越烂: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);   // 反复调用更快
  • 极快——比 SpEL 快数倍

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 强在哪

AviatorDrools
单条表达式✓(更复杂)
规则集合
条件 → 动作✓ when/then
规则间推理✓(Rete 算法)
优先级✓ salience
规则库管理✓ KieBase
业务可维护性△ 程序员✓ Drools Workbench / 工具
学习成本

Drools 的典型场景

  • 复杂风控:100+ 条规则,规则之间有依赖
  • 保险/金融:精算规则、产品定价规则
  • 审批引擎:审批节点、条件分支
  • 促销引擎:满减、买赠、阶梯优惠组合

Drools 的痛点

  1. 学习曲线陡——DRL 语法、KieSession、KieBase、AgendaGroup 等概念多
  2. 运维复杂——规则更新机制、Workbench 部署、版本管理
  3. 性能不一定快——Rete 算法在小规模规则下未必比 if-else 快
  4. 调试困难——规则匹配链条难追踪

不是所有"规则多变"的业务都需要 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 规则先于首单规则。

优点:规则之间能自动推理、能并行命中、可视化工具。

缺点:上手成本高、运维复杂、调试难。


五、选型决策

给一个直接的工程建议:

轻量场景优先 Aviator + 配置中心;只有规则数量上百、规则间有推理、且团队有人专门维护时,才值得引入 Drools 这种完整框架。


六、踩坑提醒

Aviator 篇

1. 表达式注入风险:用户输入直接执行表达式可能被构造恶意代码:

1
2
3
// ❌ 危险
String expr = userInput;
AviatorEvaluator.execute(expr);

要么白名单校验,要么把表达式只配在管理后台(运维/产品才能改)。

2. 类型转换:JSON 反序列化的 Number 在 Aviator 里可能是 LongDouble,比较时要小心:

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
  • 规则一定要版本管理 + 审计——比引擎选型更重要

把规则与代码解耦,业务才能跑得动。

使用 Hugo 构建
主题 StackJimmy 设计