3. 规则语法入门:DRL语法基础、条件(when)与动作(then)编写、规则属性(salience、no-loop等)、规则文件结构

说实话,很多刚接触规则引擎的朋友,上来就盯着复杂的业务逻辑看。我个人的建议是——先把 DRL 语法啃透。语法是地基,地基不稳,后面写再多规则都是给自己挖坑。

DRL 全称是 Drools Rule Language。它是 Drools 规则引擎的原生语言。说白了,就是用一种接近自然语言的方式,把「如果…就…」的逻辑写出来。你想想看,业务人员读得懂,开发人员也维护得了,这才是规则引擎的初衷。

核心要点:DRL 文件本质上是一个「条件 → 动作」的映射集合。引擎会帮你自动匹配条件,执行动作。你只需要关心规则怎么写,不用关心引擎怎么跑。

3.1 规则文件结构:一个 DRL 文件长什么样

我们先看一个完整的 DRL 文件骨架。我习惯把它拆成三块:头部声明、全局变量、规则体。

package com.example.rules

import com.example.model.Order
import com.example.model.Customer

global com.example.service.DiscountService discountService

rule "新用户首单折扣"
    salience 10
    no-loop true
    when
        $o: Order(totalAmount > 100)
        $c: Customer(isNew == true)
    then
        discountService.applyDiscount($o, 0.2);
        System.out.println("新用户首单满100,打8折");
end

嗯,这里要注意几个点:

  • package:和 Java 的包名类似,用来隔离规则集。不同包下的规则可以互相引用,但我建议一个 DRL 文件只放一个包。
  • import:导入 Java 类。规则里用到的 Fact 对象,必须 import。
  • global:全局变量。可以理解成规则里的「全局服务」。我曾在项目中用 global 传入了日志服务,方便规则执行时打日志。
  • rule … end:规则体。一个 DRL 文件可以包含多个 rule。

我的习惯:每个 DRL 文件只放 10~15 条规则。超过这个数,文件会变得很难维护。按业务模块拆分,比如 order-discount.drl、risk-control.drl,比堆在一个文件里强得多。

3.2 条件(when)怎么写:匹配你想要的 Fact

when 部分就是规则的条件。引擎会在工作内存(Working Memory)里找所有符合条件的 Fact 对象。说白了,就是「从一堆数据里筛出我要的」。

最基本的写法是这样的:

when
    $order: Order(amount > 500, status == "PAID")
then
    // 动作
end

这里 $order 是一个绑定变量。你可以在 then 部分用它。条件里支持多种操作符:

操作符 说明 示例
> < >= <= 数值比较 amount > 100
== != 等于 / 不等于 status == "ACTIVE"
matches 正则匹配(字符串) name matches "张.*"
contains 集合包含 tagList contains "VIP"
not 取反 not (amount > 1000)
exists 存在匹配 exists Order(amount > 1000)

多个条件之间默认是 与 关系。如果你想用「或」,可以用 || 或者 or:

when
    $o: Order(amount > 1000 || customerLevel == "VIP")
then
    // 高价值订单或VIP客户
end

我曾经踩过的坑:在条件里直接调用了 Java 方法,比如 order.isExpired()。这在语法上没问题,但每次规则匹配都会调用一次。如果这个方法里有数据库查询或远程调用,性能直接崩掉。记住,条件里尽量用字段直接比较,别搞复杂逻辑。

3.3 动作(then)怎么写:执行你的业务逻辑

then 部分就是规则命中后要执行的动作。这里可以写 Java 代码,也可以调用 global 服务,或者调用 Fact 对象的方法。

then
    $order.setDiscount(0.15);
    $order.setRemark("VIP客户自动折扣");
    discountService.sendNotification($order);
    update($order);  // 通知引擎该对象已更新
end

这里有个关键点——update()。如果你修改了 Fact 对象的字段,并且希望引擎重新评估所有规则,必须调用 update()。否则引擎不知道数据变了,后续规则可能不会触发。

常用的动作操作:

  • 直接赋值:$obj.setField(value)
  • 调用服务:globalService.doSomething($obj)
  • 插入新 Fact:insert(new ResultObject(...)) — 这会触发新的规则匹配
  • 删除 Fact:retract($obj) — 从工作内存中移除
  • 更新 Fact:update($obj) — 通知引擎重新评估

我个人的建议:then 部分尽量保持轻量。复杂的业务逻辑封装到 Java 服务里,规则只做「判断 + 调用」。这样规则文件干净,也方便单元测试。

3.4 规则属性:控制规则的行为

规则属性写在 rule 名称后面,when 前面。它们控制规则的执行顺序、是否重复触发、是否开启日志等。我挑几个最常用的讲。

3.4.1 salience:优先级

salience 的值越高,规则越先执行。默认是 0。你可以设成负数,表示低优先级。

rule "紧急规则"
    salience 100
when
    // ...
then
    // 先执行
end

rule "普通规则"
    salience 0
when
    // ...
then
    // 后执行
end

我在项目中遇到过一个问题:多条规则同时命中,但执行顺序错了,导致数据覆盖。后来给关键规则加了 salience,问题就解决了。

3.4.2 no-loop:防止死循环

no-loop 默认是 false。如果规则的动作里调用了 update(),并且条件仍然满足,这条规则会再次触发。这就可能形成死循环。

rule "防循环规则"
    no-loop true
when
    $o: Order(amount > 100)
then
    $o.setAmount(50);
    update($o);  // 即使条件还满足,也不会再次触发本规则
end

嗯,这里要注意:no-loop true 只阻止当前规则重新触发自己。如果其他规则也匹配了同一个 Fact,它们还是会触发。

3.4.3 其他常用属性

属性 作用 示例
activation-group 同一组内,只有一条规则会执行(互斥) activation-group "payment"
date-effective 规则生效日期(之后才触发) date-effective "2025-01-01"
date-expires 规则失效日期(之后不再触发) date-expires "2025-12-31"
enabled 是否启用(true/false) enabled false
duration 延迟执行(毫秒) duration 3000

我曾经踩过的坑:给所有规则都设了很高的 salience,以为这样能「优先执行」。结果 salience 值之间差距太小,执行顺序还是乱的。我的经验是:salience 值之间至少差 10,比如 100、90、80,这样排序才可靠。

3.5 知识体系总览

下面这张图,把 DRL 语法的核心结构串起来了。你可以把它当成一张「语法地图」,写规则时对照着看。

DRL 语法核心结构 DRL 文件 头部声明 package / import / global 规则体 rule … end 全局变量 global 服务/数据 规则属性 salience / no-loop / activation-group 条件 when Fact 匹配 / 操作符 / 绑定变量 动作 then 赋值 / 调用 / insert / update 💡 一个 DRL 文件可包含多个 rule,每个 rule 独立匹配,互不干扰 💡 条件中尽量用字段直接比较,避免调用外部方法,否则性能会急剧下降

3.6 实战建议:从零开始写你的第一条规则

如果你现在准备动手,我建议按这个步骤来:

  1. 先定义 Fact 类:比如 Order.java,字段用基本类型和 String,别搞复杂嵌套。
  2. 写一个最简单的规则:只匹配一个条件,then 里只打一行日志。先跑通再说。
  3. 加属性控制:试试 salience 和 no-loop,看看执行顺序的变化。
  4. 加多个条件:用 && 和 || 组合,感受一下引擎的匹配逻辑。
  5. 加动作:调用 update() 或 insert(),观察规则链式触发。

一个小技巧:调试规则时,可以在 then 里加 System.out.println("规则 " + drools.getRule().getName() + " 被触发");。这样你能清楚看到每条规则是否被命中,以及执行顺序。等上线前再删掉这些日志。

DRL 语法其实不复杂。你只要记住:when 是筛子,then 是动作,属性是控制器。把这三块搞明白,大部分业务场景都能覆盖。剩下的,就是在实战中慢慢积累经验了。