在企业级应用开发中,经常会遇到大量复杂且频繁变化的业务规则,例如会员折扣、订单优惠、风险评估、贷款审批以及动态定价等。
如果直接使用大量 if-else 实现这些逻辑,随着业务不断发展,代码会变得越来越复杂。规则之间相互交叉时,修改一项规则甚至可能影响其他业务逻辑。
Drools 是一款基于 Java 的开源规则引擎,可以将业务规则从普通 Java 代码中分离出来,使复杂的判断逻辑更加清晰,也方便后续维护和扩展。
本文将介绍 Drools 的核心概念,并通过一个订单折扣案例演示 Drools 的基本使用方法。
文章目录
-
- [一、什么是 Drools?](#一、什么是 Drools?)
- [二、Drools 的优势](#二、Drools 的优势)
-
- [2.1 业务规则与程序代码解耦](#2.1 业务规则与程序代码解耦)
- [2.2 规则更容易维护](#2.2 规则更容易维护)
- [2.3 支持复杂的规则组合](#2.3 支持复杂的规则组合)
- [2.4 方便扩展](#2.4 方便扩展)
- [2.5 支持状态推理](#2.5 支持状态推理)
- [三、Drools 的核心概念](#三、Drools 的核心概念)
-
- [3.1 Fact](#3.1 Fact)
- [3.2 Rule](#3.2 Rule)
- [3.3 KieContainer](#3.3 KieContainer)
- [3.4 KieBase](#3.4 KieBase)
- [3.5 KieSession](#3.5 KieSession)
- [3.6 Agenda](#3.6 Agenda)
- [四、创建 Drools 项目](#四、创建 Drools 项目)
-
- [4.1 添加 Maven 依赖](#4.1 添加 Maven 依赖)
- 五、创建订单实体类
- [六、编写 Drools 规则](#六、编写 Drools 规则)
-
- [6.1 salience](#6.1 salience)
- [6.2 activation-group](#6.2 activation-group)
- [七、配置 KIE 模块](#七、配置 KIE 模块)
- 八、执行规则
- [九、常用 DRL 语法](#九、常用 DRL 语法)
-
- [9.1 多条件判断](#9.1 多条件判断)
- [9.2 逻辑或](#9.2 逻辑或)
- [9.3 绑定属性](#9.3 绑定属性)
- [9.4 修改 Fact](#9.4 修改 Fact)
- [9.5 插入和删除 Fact](#9.5 插入和删除 Fact)
- [十、KieSession 与 StatelessKieSession 的区别](#十、KieSession 与 StatelessKieSession 的区别)
-
- [10.1 KieSession](#10.1 KieSession)
- [10.2 StatelessKieSession](#10.2 StatelessKieSession)
- [十一、Drools 的常见应用场景](#十一、Drools 的常见应用场景)
- [十二、Drools 最佳实践](#十二、Drools 最佳实践)
-
- [12.1 一条规则只处理一个明确目标](#12.1 一条规则只处理一个明确目标)
- [12.2 使用有业务含义的规则名称](#12.2 使用有业务含义的规则名称)
- [12.3 避免在 then 中编写复杂业务代码](#12.3 避免在 then 中编写复杂业务代码)
- [12.4 控制规则之间的依赖](#12.4 控制规则之间的依赖)
- [12.5 不要滥用高优先级](#12.5 不要滥用高优先级)
- [12.6 为规则编写单元测试](#12.6 为规则编写单元测试)
- [12.7 记录规则执行情况](#12.7 记录规则执行情况)
- [12.8 注意会话的线程安全](#12.8 注意会话的线程安全)
- [十三、Drools 的不足](#十三、Drools 的不足)
- 十四、总结
- 参考资料
一、什么是 Drools?
Drools 是一个开源的业务规则管理系统,其核心能力是根据输入的数据匹配业务规则,并执行满足条件的规则。
在传统代码中,我们可能会这样编写折扣逻辑:
java
if ("VIP".equals(order.getCustomerType())
&& order.getOriginalPrice() >= 1000) {
order.setDiscountRate(0.20);
} else if ("NORMAL".equals(order.getCustomerType())
&& order.getOriginalPrice() >= 1000) {
order.setDiscountRate(0.10);
} else {
order.setDiscountRate(0);
}
当规则数量较少时,这种写法没有明显问题。但如果系统中存在几十甚至上百条规则,代码中就会出现大量嵌套判断,维护难度也会迅速增加。
使用 Drools 后,可以将规则写在独立的 .drl 文件中:
text
rule "VIP订单满1000元享八折"
when
$order : Order(
customerType == "VIP",
originalPrice >= 1000
)
then
$order.setDiscountRate(0.20);
end
Java 代码负责提供数据,Drools 负责匹配并执行规则,从而实现业务代码与规则逻辑的分离。
二、Drools 的优势
2.1 业务规则与程序代码解耦
业务规则存放在独立的 DRL 文件中,不需要将所有判断逻辑都写在 Java 代码里。
2.2 规则更容易维护
每条规则通常由条件和结果组成,结构比多层 if-else 更清晰。
2.3 支持复杂的规则组合
Drools 支持优先级、规则分组、规则互斥、时间规则和事件处理等功能,适合处理复杂的业务判断。
2.4 方便扩展
新增业务规则时,通常只需要增加新的规则文件或规则内容,不需要大范围修改原有业务代码。
2.5 支持状态推理
在有状态会话中,一条规则可以修改或插入新的事实,从而继续触发其他规则,形成规则链。
三、Drools 的核心概念
3.1 Fact
Fact 表示需要交给规则引擎处理的业务数据,本质上通常是一个普通 Java 对象。当订单对象被插入 Drools 会话后,它就成为规则引擎中的一个 Fact。
3.2 Rule
Rule 表示一条业务规则,通常由规则名称、条件和动作组成:
text
rule "规则名称"
when
// 规则条件
then
// 条件满足后执行的操作
end
其中,when 用于定义规则生效的条件,then 用于定义条件满足后执行的操作。
3.3 KieContainer
KieContainer 用于加载和管理项目中的规则资源。它会读取项目中的规则文件和 KIE 配置,并创建相应的 KieBase 和 KieSession。
3.4 KieBase
KieBase 是规则知识库,其中保存已经编译完成的规则、函数和类型定义。KieBase 本身不保存运行时业务数据。
3.5 KieSession
KieSession 是规则引擎的执行会话,负责接收 Fact、匹配规则、执行规则以及保存运行过程中的状态。
常见会话分为两类:
- KieSession:有状态会话,会保留插入的数据和规则执行状态;
- StatelessKieSession:无状态会话,每次执行相对独立,适合校验、计算和过滤等场景。
3.6 Agenda
当 Fact 满足规则条件时,符合条件的规则会进入 Agenda。Drools 会根据规则优先级和规则分组等配置决定实际执行顺序。
四、创建 Drools 项目
下面通过"订单折扣计算"案例演示 Drools 的基本使用方法。本文示例采用传统 KIE API,Drools 版本使用 10.1.0,JDK 使用 17。
项目结构如下:
text
drools-demo
├── pom.xml
└── src
└── main
├── java
│ └── com/example/drools
│ ├── DroolsApplication.java
│ └── model/Order.java
└── resources
├── META-INF/kmodule.xml
└── rules/order-discount.drl
4.1 添加 Maven 依赖
在 pom.xml 中添加 Drools 依赖:
xml
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.kie</groupId>
<artifactId>kie-api</artifactId>
<version>10.1.0</version>
</dependency>
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-engine</artifactId>
<version>10.1.0</version>
</dependency>
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-xml-support</artifactId>
<version>10.1.0</version>
</dependency>
</dependencies>
kie-api 提供 KIE 相关接口,drools-engine 提供规则引擎的核心实现,drools-xml-support 用于支持 XML 形式的 KIE 配置。
五、创建订单实体类
创建 Order 类:
java
package com.example.drools.model;
public class Order {
private String customerType;
private double originalPrice;
private double discountRate;
private double finalPrice;
private String message;
public Order() {
}
public Order(String customerType, double originalPrice) {
this.customerType = customerType;
this.originalPrice = originalPrice;
}
public String getCustomerType() {
return customerType;
}
public void setCustomerType(String customerType) {
this.customerType = customerType;
}
public double getOriginalPrice() {
return originalPrice;
}
public void setOriginalPrice(double originalPrice) {
this.originalPrice = originalPrice;
}
public double getDiscountRate() {
return discountRate;
}
public void setDiscountRate(double discountRate) {
this.discountRate = discountRate;
}
public double getFinalPrice() {
return finalPrice;
}
public void setFinalPrice(double finalPrice) {
this.finalPrice = finalPrice;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
@Override
public String toString() {
return "Order{" +
"customerType='" + customerType + '\\'' +
", originalPrice=" + originalPrice +
", discountRate=" + discountRate +
", finalPrice=" + finalPrice +
", message='" + message + '\\'' +
'}';
}
}
为了让示例保持简单,这里使用 double 表示金额。实际涉及金额计算的生产项目应优先使用 BigDecimal,避免浮点数精度问题。
六、编写 Drools 规则
在 src/main/resources/rules 目录下创建 order-discount.drl:
text
package com.example.rules
import com.example.drools.model.Order
rule "VIP订单满1000元享八折"
salience 20
activation-group "order-discount"
when
$order : Order(
customerType == "VIP",
originalPrice >= 1000
)
then
$order.setDiscountRate(0.20);
$order.setFinalPrice($order.getOriginalPrice() * 0.80);
$order.setMessage("VIP订单满1000元,享受8折优惠");
end
rule "普通订单满1000元享九折"
salience 10
activation-group "order-discount"
when
$order : Order(
customerType == "NORMAL",
originalPrice >= 1000
)
then
$order.setDiscountRate(0.10);
$order.setFinalPrice($order.getOriginalPrice() * 0.90);
$order.setMessage("普通订单满1000元,享受9折优惠");
end
rule "默认不打折"
salience 0
activation-group "order-discount"
when
$order : Order()
then
$order.setDiscountRate(0);
$order.setFinalPrice($order.getOriginalPrice());
$order.setMessage("当前订单不满足优惠条件");
end
这里使用了两个重要的规则属性。
6.1 salience
salience 表示规则优先级,数值越大,规则通常越优先执行。在这个案例中,VIP 优惠规则优先级为 20,普通用户优惠规则优先级为 10,默认规则优先级为 0。
6.2 activation-group
同一个 activation-group 中,只要有一条规则被执行,其他已经激活的规则就会被取消。因此,一个订单只会执行一条折扣规则,避免优惠结果被默认规则覆盖。
七、配置 KIE 模块
在 src/main/resources/META-INF 目录下创建 kmodule.xml:
xml
<?xml version="1.0" encoding="UTF-8"?>
<kmodule xmlns="http://www.drools.org/xsd/kmodule">
<kbase name="orderKieBase"
packages="com.example.rules"
default="true">
<ksession name="orderKieSession"
default="true"/>
</kbase>
</kmodule>
packages 属性对应的是 DRL 文件顶部声明的包名,而不是 DRL 文件所在的文件夹路径。
八、执行规则
创建启动类 DroolsApplication:
java
package com.example.drools;
import com.example.drools.model.Order;
import org.kie.api.KieServices;
import org.kie.api.runtime.KieContainer;
import org.kie.api.runtime.KieSession;
public class DroolsApplication {
public static void main(String[] args) {
KieServices kieServices = KieServices.Factory.get();
KieContainer kieContainer =
kieServices.getKieClasspathContainer();
KieSession kieSession =
kieContainer.newKieSession("orderKieSession");
try {
Order order = new Order("VIP", 1200);
kieSession.insert(order);
int firedRules = kieSession.fireAllRules();
System.out.println("执行规则数量:" + firedRules);
System.out.println("订单结果:" + order);
} finally {
kieSession.dispose();
}
}
}
运行程序后,可以得到类似结果:
text
执行规则数量:1
订单结果:Order{customerType='VIP', originalPrice=1200.0,
discountRate=0.2, finalPrice=960.0,
message='VIP订单满1000元,享受8折优惠'}
代码执行流程如下:
- 创建 KieContainer;
- 从容器中创建 KieSession;
- 将订单对象插入会话;
- 调用 fireAllRules() 匹配并执行规则;
- 获取规则修改后的订单;
- 调用 dispose() 释放会话资源。
对于有状态的 KieSession,使用完成后应调用 dispose(),避免会话长期持有对象而造成资源泄漏。
九、常用 DRL 语法
9.1 多条件判断
text
$order : Order(
customerType == "VIP",
originalPrice >= 1000
)
多个条件之间使用逗号分隔,表示逻辑与。
9.2 逻辑或
text
$order : Order(
customerType == "VIP" || customerType == "SVIP"
)
9.3 绑定属性
text
$order : Order($price : originalPrice)
绑定后可以在 then 中使用 $price。
9.4 修改 Fact
在有状态会话中,如果希望修改 Fact 后重新触发规则匹配,可以使用 modify:
text
modify($order) {
setDiscountRate(0.20),
setFinalPrice($order.getOriginalPrice() * 0.80)
}
modify 会通知规则引擎对象已经发生变化。使用时需要注意规则是否会再次匹配,避免形成无限循环。
9.5 插入和删除 Fact
可以通过 insert 插入新的 Fact,也可以通过 delete 删除 Fact。新对象插入后可能继续触发其他规则,从而形成规则链。
十、KieSession 与 StatelessKieSession 的区别
10.1 KieSession
KieSession 会保留会话状态,适合以下场景:
- 多个 Fact 之间存在关联;
- 一条规则会触发其他规则;
- 需要修改、删除或持续维护 Fact;
- 需要进行复杂推理;
- 需要处理连续事件。
使用结束后应调用 dispose()。
10.2 StatelessKieSession
StatelessKieSession 更像一个普通函数:传入数据,执行规则,然后返回结果。
java
StatelessKieSession session =
kieContainer.newStatelessKieSession();
session.execute(order);
它适合数据校验、单次价格计算、消息分类、请求路由等不需要保存历史状态的决策。无状态会话执行 execute() 时,会自动完成数据插入、规则执行和会话清理。
十一、Drools 的常见应用场景
- 营销优惠:根据会员等级、订单金额和活动时间计算优惠;
- 风控系统:根据用户信用、交易金额和设备信息判断风险;
- 审批流程:根据金额、部门和申请类型决定审批流程;
- 保险金融:根据年龄、职业和历史记录计算保费或授信额度;
- 数据校验:对提交的数据执行多项校验并返回失败原因;
- 智能推荐:根据用户特征和业务策略匹配推荐结果。
十二、Drools 最佳实践
12.1 一条规则只处理一个明确目标
不要在一条规则中混合过多业务逻辑。规则越独立,后期测试和维护越容易。
12.2 使用有业务含义的规则名称
"VIP订单满1000元享八折"比"rule_001"更加容易理解和排查。
12.3 避免在 then 中编写复杂业务代码
数据库访问、远程调用和复杂计算最好放在 Java 服务中完成。规则负责做出决策,Java 服务负责执行具体操作。
12.4 控制规则之间的依赖
大量规则相互修改 Fact 时,可能形成难以理解的执行链。应合理使用 salience、agenda-group、activation-group 和 no-loop 等属性。
12.5 不要滥用高优先级
如果每条规则都依赖 salience 控制顺序,规则集会变得难以维护。规则优先级应该只用于确实存在业务先后关系的场景。
12.6 为规则编写单元测试
测试应覆盖满足条件、不满足条件、临界值、多条规则同时匹配以及空值或异常输入。
12.7 记录规则执行情况
可以通过 Agenda 事件监听器记录哪些规则被匹配和执行,避免在大量规则中直接编写 System.out.println()。
12.8 注意会话的线程安全
不要在多个请求线程之间随意共享同一个有状态 KieSession。常见做法是根据业务请求创建独立会话,或者使用会话池。
十三、Drools 的不足
Drools 并不适合所有项目,它也存在一定的使用成本:
- DRL 具有独立语法,需要一定学习时间;
- 规则数量较多时,执行顺序可能不容易理解;
- 不合理的规则可能产生循环触发;
- 排查问题通常比普通 Java 判断更加复杂;
- 简单业务使用规则引擎可能属于过度设计;
- 团队需要建立规则审核、测试和版本管理机制。
如果系统只有少量且几乎不会变化的判断逻辑,普通 Java 代码可能更加直接。当规则数量多、变化频繁,并且需要由多个业务维度共同决策时,Drools 才能更明显地体现价值。
十四、总结
Drools 的核心价值不是消除所有 if-else,而是将复杂、频繁变化的业务决策从程序流程中分离出来。
Drools 的基本使用流程可以概括为:
text
定义业务对象
↓
编写 DRL 规则
↓
创建 KieContainer
↓
创建 KieSession
↓
插入 Fact
↓
执行规则
↓
获取处理结果
↓
释放会话资源
在实际项目中,引入 Drools 前需要认真判断业务是否具有规则复杂、变化频繁和需要统一管理等特点。如果只是为了替换几个简单的条件判断,引入规则引擎反而可能增加系统复杂度。
只有当规则确实需要独立管理、组合执行和持续扩展时,Drools 才是一把真正合适的工具。