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