规则语言DRL:语法结构、包声明、import、全局变量、函数、查询
好,咱们今天来啃一块硬骨头——DRL语法。说实话,我刚接触Drools那会儿,看着这堆规则文件也是一脸懵。但后来我发现,只要把它的骨架摸清楚,写规则就跟填表格一样简单。
DRL的全称是Drools Rule Language,说白了就是Drools自己的一套规则描述语言。它不像Java那么啰嗦,也不像YAML那么死板。我个人习惯把它看作「带逻辑的配置文件」。
一、DRL的语法结构
一个完整的DRL文件,通常长这样:
package com.example.rules
import com.example.model.Order
import com.example.model.Customer
global com.example.service.NotificationService notificationService
function boolean isVipCustomer(Customer c) {
return c.getTotalSpent() > 10000;
}
query "findHighValueOrders"
$order: Order(amount > 5000)
end
rule "VIP客户优先调度"
when
$c: Customer(vip == true)
$o: Order(customerId == $c.id, status == "pending")
then
$o.setPriority(1);
notificationService.notify("VIP订单已优先调度");
update($o);
end
嗯,这里要注意,DRL文件里每个部分都有它存在的意义。咱们一个一个拆开讲。
二、包声明(package)
包声明,说白了就是给规则文件起个命名空间。跟Java里的package一个道理。
package com.energystorage.rules.dispatch
我在项目中遇到过一个问题:两个团队各自写了规则,结果包名冲突了,规则互相覆盖。后来我们强制规定,包名必须跟模块路径一致,比如调度模块就用dispatch,告警模块就用alert。
三、import(导入)
import的作用,就是把Java类引入到规则里。你想想看,规则里要操作订单、电池、调度任务,这些对象总得有个来源吧?
import com.energystorage.model.Battery
import com.energystorage.model.DispatchTask
import java.util.List
我个人习惯把import分成三组:
- 模型类:Battery、Order、Customer这些实体
- 工具类:List、Map、Date这些
- 服务类:全局变量里要用到的服务
为什么要分组?因为规则文件一多,import乱成一锅粥,你根本不知道哪个类是从哪来的。
四、全局变量(global)
全局变量,是DRL里一个很特别的存在。它允许你在规则里引用外部的Java对象,比如日志服务、数据库连接、消息队列。
global com.energystorage.service.LogService logService
global com.energystorage.service.AlertService alertService
但是!这里有个大坑——全局变量不能在规则里被修改。你只能用它,不能改它。为什么?因为Drools的规则引擎是并发执行的,多个规则同时修改一个全局变量,那不乱套了?
我曾经有个同事,非要在规则里给全局变量赋值,结果跑出来的结果每次都不一样。排查了两天才发现,是全局变量被多个规则线程同时写了。
五、函数(function)
函数,就是DRL里的「小工具」。当你在多个规则里重复写同一段逻辑时,就该把它抽成函数了。
function boolean isPeakHour(int hour) {
return hour >= 10 && hour <= 14 || hour >= 18 && hour <= 21;
}
function double calculateChargeCost(double price, double quantity) {
return price * quantity * 0.95; // 充电有5%损耗
}
你想想看,如果没有函数,每个规则里都要写一遍hour >= 10 && hour <= 14,万一哪天高峰时段变了,你得改多少个地方?
我个人习惯把函数放在DRL文件的顶部,紧跟在import后面。这样阅读规则时,先看到工具函数,再看具体规则,思路很清晰。
Math.max()、String.format(),这些都能直接用。
六、查询(query)
查询,是DRL里一个被很多人忽略的功能。它允许你在规则引擎的工作内存里,按条件查找Fact对象。
query "findBatteriesBySoc"
$b: Battery(soc > 80)
end
query "findOrdersByCustomer"
$c: Customer()
$o: Order(customerId == $c.id)
end
查询有什么用?我举两个实际场景:
- 调试时:你想看看当前工作内存里有哪些电池的SOC超过80%,直接调这个查询就行
- 外部调用时:Java代码里可以调用
ksession.getQueryResults("findBatteriesBySoc"),拿到结果列表
嗯,这里要注意,查询不会触发规则。它只是「看一眼」,不会修改任何数据。所以你可以放心地用。
七、知识体系总览
说了这么多,咱们用一张图把DRL的骨架串起来:
这张图把DRL的六个核心部分串起来了。从上到下,依次是包声明、import、全局变量、函数、查询,最后是规则。每一层都有它自己的职责,缺一不可。
八、实战中的避坑指南
最后,分享几个我踩过的坑:
- 包名别乱写:我曾经把包名写成
com.energy.storage,结果跟另一个模块的包名冲突了。后来统一改成com.energystorage.dispatch,再没出过问题。 - import别偷懒:有人喜欢用
import com.energystorage.model.*,但Drools不支持通配符导入。老老实实一行一个类。 - 全局变量别乱改:记住,全局变量是只读的。想改数据?用Fact对象加update()。
- 函数别写太复杂:函数里不要写超过20行的逻辑。太复杂了,不如拆成Java工具类。
- 查询别滥用:查询适合调试和外部调用,但不要在规则里频繁调查询,性能会受影响。
好了,DRL的语法结构就讲到这里。记住这六个核心部分,写规则就跟搭积木一样简单。下一节,咱们会深入规则的条件部分——when,看看怎么写出精准的匹配条件。
公众号:蓝海资料掘金营,微信deep3321