4、规则引擎实战:Drools规则引擎入门、规则编写语法、规则冲突解决

4.1 为什么选Drools?

说到规则引擎,Drools是绕不开的。我最早接触它是在一个金融风控项目里,业务方天天改规则,改完还得重新部署。那会儿我就想,能不能把规则从代码里抽出来?Drools正好干这个。

说白了,Drools就是一个「规则执行器」。你把业务规则写成.drl文件,它帮你匹配、执行。我个人的习惯是,只要规则变化频率超过一个月一次,就值得上规则引擎。

核心价值:规则与代码分离,业务人员也能看懂、修改规则。

4.2 快速入门:第一个Drools程序

先别急着看复杂语法。咱们从最简单的开始——判断订单金额是否达标。

4.2.1 环境准备

Maven项目里加依赖就行。我用的是7.x版本,稳定。

<dependency>
    <groupId>org.drools</groupId>
    <artifactId>drools-core</artifactId>
    <version>7.73.0.Final</version>
</dependency>
<dependency>
    <groupId>org.drools</groupId>
    <artifactId>drools-compiler</artifactId>
    <version>7.73.0.Final</version>
</dependency>

4.2.2 写一个规则文件

规则文件后缀是.drl。我习惯把它放在resources/rules目录下。

// order-discount.drl
package com.example.rules

import com.example.model.Order

rule "满100减10"
    when
        $o: Order(amount >= 100)
    then
        $o.setDiscount(10);
        System.out.println("订单满100,减10元");
end

你看,结构很清晰:rule关键字定义规则名,when写条件,then写动作。我在项目中遇到过新手把then写成do,结果编译报错半天找不出原因。

4.2.3 在Java里调用

KieServices ks = KieServices.Factory.get();
KieContainer kContainer = ks.getKieClasspathContainer();
KieSession kSession = kContainer.newKieSession();

Order order = new Order(150.0);
kSession.insert(order);
kSession.fireAllRules();
kSession.dispose();

System.out.println("最终折扣:" + order.getDiscount());

嗯,这里要注意:fireAllRules()执行完后,记得dispose()释放会话。我曾经在一个批处理任务里忘了调这个方法,结果内存泄漏了。

4.3 规则编写语法详解

Drools的语法其实不复杂。你把它想象成「如果...那么...」的翻译就行。

4.3.1 基本模式

语法元素 说明 示例
rule 定义规则 rule "规则名"
when 条件部分 $o: Order(amount > 100)
then 动作部分 $o.setDiscount(10);
end 规则结束 end

4.3.2 条件组合

多个条件用&&||连接。我建议用&&,可读性更好。

rule "VIP用户且金额大于200"
    when
        $o: Order(amount > 200, customerType == "VIP")
    then
        $o.setDiscount(20);
end

你想想看,如果条件写成amount > 200 && customerType == "VIP",是不是一眼就能看懂?

4.3.3 多模式匹配

有时候需要匹配多个对象。比如「用户是VIP,且订单金额大于500」。

rule "VIP大额订单"
    when
        $u: User(type == "VIP")
        $o: Order(amount > 500, userId == $u.id)
    then
        $o.setDiscount(30);
        System.out.println("VIP大额订单,额外优惠");
end

这里$u$o是变量名,可以随便起。我个人习惯用$前缀,方便区分。

4.4 规则冲突解决

规则多了,冲突就来了。比如两条规则都匹配同一个订单,到底执行哪条?

4.4.1 冲突是怎么产生的?

举个例子:

rule "规则A:满100减10"
    when
        $o: Order(amount >= 100)
    then
        $o.setDiscount(10);
end

rule "规则B:满200减30"
    when
        $o: Order(amount >= 200)
    then
        $o.setDiscount(30);
end

订单金额250,两条规则都匹配。先执行A还是B?默认情况下,Drools按规则名排序。但这样不可控。

4.4.2 解决方案一:优先级

salience属性。数值越大,优先级越高。

rule "规则B:满200减30"
    salience 10
    when
        $o: Order(amount >= 200)
    then
        $o.setDiscount(30);
end

rule "规则A:满100减10"
    salience 5
    when
        $o: Order(amount >= 100)
    then
        $o.setDiscount(10);
end

这样B先执行,A后执行。但注意,如果两条规则都修改了同一个字段,后执行的会覆盖前面的。我曾经踩过这个坑——先减了30,又被减10覆盖了。

避坑指南:如果多条规则修改同一个字段,建议用activation-group让它们互斥,或者只保留一条规则。

4.4.3 解决方案二:激活组

activation-group让同一组内的规则只执行一条。

rule "规则A:满100减10"
    activation-group "discount"
    when
        $o: Order(amount >= 100)
    then
        $o.setDiscount(10);
end

rule "规则B:满200减30"
    activation-group "discount"
    when
        $o: Order(amount >= 200)
    then
        $o.setDiscount(30);
end

这样,如果B匹配了,A就不会执行。说白了就是「二选一」。

4.4.3 解决方案三:规则流

复杂场景下,可以用规则流(Rule Flow)控制执行顺序。这个我用的不多,但大型项目里很常见。

我的建议:小项目用salience就够了。规则超过20条,建议用activation-group分组。超过50条,考虑规则流。

4.5 实战经验总结

最后分享几个我在项目中踩过的坑:

  • 规则文件命名:别用中文名。我见过有人用「满减规则.drl」,结果Linux上编码问题报错。
  • 调试技巧:then里加System.out.println,或者用Drools的kmodule.xml开启日志。
  • 性能问题:规则太多时,fireAllRules()会变慢。建议用fireUntilHalt()配合异步处理。
  • 版本管理:规则文件一定要纳入Git管理。别问我为什么知道——有一次我手误删了生产环境的规则文件,差点背锅。

嗯,Drools入门其实不难。关键是理解「规则即数据」这个思想。你把它当成一种配置,而不是代码,思路就打开了。