1. 从“硬编码”到“规则引擎”:为什么我们需要Drools?
如果你写过业务系统,尤其是那些涉及复杂业务逻辑、频繁变更策略的系统,比如风控、营销活动、计费结算,那你一定对下面这种代码深恶痛绝:
if (user.getLevel() == VIP && order.getAmount() > 1000 && promotion.isActive()) {
discount = order.getAmount() * 0.1;
if (discount > 200) {
discount = 200;
}
// ... 可能还有一堆其他逻辑
} else if (user.getLevel() == NORMAL && order.getAmount() > 5000) {
// 另一套逻辑
}
// ... 更多的if-else
这种“硬编码”的业务逻辑,有几个致命伤: 第一,任何策略调整都需要开发人员修改代码、重新编译、测试、上线,周期长、风险高。第二,逻辑分散在各个Service类中,难以集中管理和维护。第三,业务人员(产品、运营)看不懂代码,无法直接参与规则的制定和验证,沟通成本巨大。
Drools规则引擎就是为了解决这些问题而生的。它的核心思想是 将业务决策逻辑从应用程序代码中剥离出来,使用一种接近自然语言的规则语法(DRL)来编写,并由专门的规则引擎来执行。 这样一来,业务规则就变成了可以独立管理、动态加载的“数据”,而非“代码”。
简单来说,Drools就像一个独立的“业务逻辑处理器”。你把事实(Fact,即业务数据对象)喂给它,它根据你预先定义好的规则库(Knowledge Base)进行匹配和推理,最后输出决策结果。这个过程,我们称之为“规则推理”。
在Java生态中,Drools是功能最强大、社区最活跃的规则引擎之一。它不仅仅是一个简单的“if-else”匹配器,其底层基于Rete算法,能够高效地处理大量、复杂的规则匹配,支持规则流、决策表等高级特性,非常适合企业级复杂业务场景。
那么,在Spring Boot这个“约定大于配置”的现代Java开发框架中,如何优雅地集成Drools,让它成为我们应用的一部分呢?这正是本文要解决的核心问题。我会带你从零开始,搭建一个Spring Boot项目,整合Drools,并通过一个完整的优惠券计算示例,让你彻底掌握其核心用法和避坑要点。
2. 环境搭建与项目初始化:避开版本兼容的“第一坑”
整合任何第三方组件,第一步永远是环境准备。对于Drools和Spring Boot,版本兼容性是首要考虑的问题,这也是新手最容易踩的坑。
2.1 创建Spring Boot项目与依赖引入
我推荐使用Spring Initializr(start.spring.io)或者IDE(如IntelliJ IDEA)直接创建项目。选择Spring Boot 2.7.x 或 3.x 版本均可,但需要注意Drools对应版本的适配。目前,
drools-spring-boot-starter
对Spring Boot 3.x的官方支持还在完善中,为了稳定起见,本文以
Spring Boot 2.7.18
和
Drools 7.73.0.Final
为例。
在你的
pom.xml
中,需要引入以下核心依赖:
<dependencies>
<!-- Spring Boot Web Starter (根据你的项目类型选择) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Drools Spring Boot Starter - 核心整合依赖 -->
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-spring-boot-starter</artifactId>
<version>7.73.0.Final</version>
</dependency>
<!-- Drools Decision Tables 支持 (可选,用于Excel决策表) -->
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-decisiontables</artifactId>
<version>7.73.0.Final</version>
</dependency>
<!-- Lombok (可选,简化实体类代码) -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- 测试依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
注意 :
drools-spring-boot-starter这个依赖非常关键,它自动帮我们配置了KieContainer、KieSession等核心Bean,并设置了规则文件的自动扫描路径(默认是classpath:/org/kie/和classpath:/rules)。这大大简化了配置工作。如果你发现规则文件没被加载,首先检查这个依赖是否引入,以及规则文件是否放在了正确的路径下。
2.2 核心配置与规则文件路径
Spring Boot和Drools Starter的自动配置已经做了大部分工作,但我们通常需要做一些微调。在
application.yml
或
application.properties
中,可以配置规则文件的路径:
# application.yml
spring:
application:
name: drools-demo
# Drools 相关配置 (非必须,使用默认即可)
# kie:
# base-dir: /rules # 可以自定义规则文件基础路径,但需要对应调整文件位置
默认情况下,Drools会扫描
src/main/resources/rules
目录下的所有
.drl
(Drools Rule Language)规则文件。这是最推荐的方式,清晰地将规则与代码分离。
现在,在
src/main/resources
下创建
rules
文件夹。我们后续的规则文件都将放在这里。
2.3 定义业务模型(Fact)
规则引擎处理的对象,我们称之为“事实”(Fact)。它就是一个普通的Java对象(POJO)。规则文件中的条件部分,就是对这些Fact的字段进行判断。
我们来定义一个简单的订单和用户模型,用于后续的优惠券计算示例:
package com.example.droolsdemo.model;
import lombok.Data;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
@Data
public class Order {
/** 订单号 */
private String orderNo;
/** 下单用户 */
private User user;
/** 原始金额 */
private BigDecimal originalAmount;
/** 实际支付金额 */
private BigDecimal payAmount;
/** 本订单应用的优惠券列表 */
private List<Coupon> coupons = new ArrayList<>();
// 一个便捷的方法,用于计算优惠后的金额
public BigDecimal calculateFinalAmount() {
BigDecimal finalAmount = this.originalAmount;
for (Coupon coupon : coupons) {
finalAmount = finalAmount.subtract(coupon.getDiscountAmount());
}
// 确保金额不为负数
return finalAmount.compareTo(BigDecimal.ZERO) < 0 ? BigDecimal.ZERO : finalAmount;
}
}
@Data
public class User {
/** 用户ID */
private Long id;
/** 用户等级:NORMAL, VIP, SVIP */
private String level;
/** 是否为新用户 */
private Boolean isNew;
/** 累计消费金额 */
private BigDecimal totalConsumption;
}
@Data
public class Coupon {
/** 优惠券名称 */
private String name;
/** 优惠类型:FIXED(固定金额), PERCENT(百分比), THRESHOLD_FULL_MINUS(满减) */
private String type;
/** 折扣金额 (对于百分比券,这里存折扣比例,如0.1代表10%) */
private BigDecimal discountAmount;
/** 使用门槛(满多少可用) */
private BigDecimal threshold;
}
这里使用了Lombok的
@Data
注解自动生成getter、setter等方法。
Order
类中的
calculateFinalAmount
方法是一个业务方法,规则引擎在执行完所有优惠券规则后,我们可以调用这个方法得到最终金额。注意,在规则中我们通常不直接调用Fact的方法去修改状态,而是通过规则的结果(RHS)来设置Fact的属性。
3. 编写你的第一条Drools规则(DRL)
规则文件的后缀是
.drl
(Drools Rule Language)。它由几个部分组成,我们通过一个具体的“新用户首单立减”规则来学习。
在
src/main/resources/rules
目录下,创建文件
discount.drl
。
// discount.drl
package rules.discount // 规则包名,逻辑分组用,类似于Java包
import com.example.droolsdemo.model.Order
import com.example.droolsdemo.model.User
import com.example.droolsdemo.model.Coupon
import java.math.BigDecimal
// 规则1: 新用户首单立减10元
rule "New User First Order Discount"
no-loop true // 防止规则触发后,因Fact更新导致自身重复触发
salience 10 // 规则优先级,数值越大越先执行
when
$order: Order($user: user)
$user: User(isNew == true) // 匹配是新用户
// 确保订单还没有应用过“新用户优惠” (简单的重复检查)
not(Coupon(name == "新用户首单礼"))
then
System.out.println("[规则触发] 新用户首单立减10元,订单号: " + $order.getOrderNo());
Coupon coupon = new Coupon();
coupon.setName("新用户首单礼");
coupon.setType("FIXED");
coupon.setDiscountAmount(new BigDecimal("10"));
coupon.setThreshold(BigDecimal.ZERO); // 无门槛
$order.getCoupons().add(coupon);
update($order); // 通知引擎Order对象发生了变化
end
// 规则2: VIP用户订单满100减15
rule "VIP Order Over 100 Discount"
no-loop true
salience 9 // 优先级比新用户规则低
when
$order: Order(originalAmount >= 100, $user: user)
User(level == "VIP" from $user)
not(Coupon(name == "VIP满100减15"))
then
System.out.println("[规则触发] VIP用户满100减15,订单号: " + $order.getOrderNo());
Coupon coupon = new Coupon();
coupon.setName("VIP满100减15");
coupon.setType("FIXED");
coupon.setDiscountAmount(new BigDecimal("15"));
coupon.setThreshold(new BigDecimal("100"));
$order.getCoupons().add(coupon);
update($order);
end
// 规则3: 所有用户,订单金额超过200,打95折(百分比折扣)
rule "Global Over 200 5% Off"
no-loop true
salience 8
when
$order: Order(originalAmount > 200)
// 注意:百分比折扣券可能与其他券叠加,这里简单处理,不检查重复
// 实际业务中需要更复杂的互斥逻辑
then
System.out.println("[规则触发] 订单满200享95折,订单号: " + $order.getOrderNo());
Coupon coupon = new Coupon();
coupon.setName("通用95折券");
coupon.setType("PERCENT");
// 折扣比例0.05,但计算金额在RHS完成
BigDecimal discount = $order.getOriginalAmount().multiply(new BigDecimal("0.05"));
coupon.setDiscountAmount(discount);
coupon.setThreshold(new BigDecimal("200"));
$order.getCoupons().add(coupon);
update($order);
end
我们来拆解一下这条规则的结构:
-
package: 规则包,用于逻辑分组。一个物理.drl文件可以包含多个包,但通常一个文件一个包,便于管理。 -
import: 导入需要使用的Java类,和Java语法一样。 -
rule "规则名": 定义一条规则的开始,规则名需唯一且具有业务含义。 -
属性:-
no-loop true: 防止规则触发后,因为自己在then部分修改了Fact(如update($order)),导致规则条件再次被满足,从而陷入无限循环。 这几乎是每条可能修改Fact的规则都必须加的属性,非常重要! -
salience 10: 优先级。默认是0,可以为负数。数值越大,优先级越高,越先被评估和执行。当多条规则同时被激活时,这个属性决定执行顺序。 在涉及规则互斥或顺序依赖时,必须谨慎设置。
-
-
when: 规则的条件部分(LHS, Left Hand Side)。这里定义了模式匹配。$order: Order(...)表示匹配一个Order类型的对象,并绑定到变量$order。$user: User(isNew == true) from $order.getUser()是一种写法,另一种更简洁的是上面示例中的嵌套属性访问。not(...)表示“不存在”,用于防止重复应用优惠。 -
then: 规则的结果部分(RHS, Right Hand Side)。当所有条件满足时,执行这里的逻辑。这里我们创建了一个Coupon对象,设置好优惠信息,并添加到订单的优惠券列表中。update($order)是 关键操作 ,它告诉规则引擎:“我修改了$order这个Fact,请重新评估所有规则,看看有没有新的规则被激活或者旧的规则失效。” 如果不调用update,引擎不会知道Fact发生了变化。 -
end: 规则结束。
实操心得 :在
then部分,尽量只做简单的赋值、计算和调用Fact的setter方法。避免在这里执行复杂的业务逻辑、数据库操作或远程调用,这会影响规则引擎的性能和确定性。复杂的逻辑应该放在普通的Spring Bean中,在规则里通过global(全局变量)引入来调用。
4. 在Spring Boot中注入与使用KieSession
环境搭好了,规则写好了,接下来就是在代码里使用它。Drools Starter已经为我们自动配置了
KieContainer
Bean。
KieContainer
是规则知识的容器,我们可以从它里面获取一个
KieSession
。
KieSession
是与规则引擎进行交互的核心会话,用于插入事实、触发规则、获取结果。
4.1 创建RuleService
我们来创建一个服务类,封装规则执行的逻辑。
package com.example.droolsdemo.service;
import com.example.droolsdemo.model.Order;
import org.kie.api.runtime.KieContainer;
import org.kie.api.runtime.KieSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class RuleService {
@Autowired
private KieContainer kieContainer;
/**
* 执行订单优惠计算规则
* @param order 订单事实
* @return 应用了优惠券后的订单
*/
public Order executeDiscountRule(Order order) {
// 1. 从KieContainer中获取一个新的KieSession
// 注意:KieSession是非线程安全的,并且通常有状态。
// 最佳实践是为每次规则执行创建一个新的Session,执行完毕后务必销毁。
KieSession kieSession = kieContainer.newKieSession();
try {
// 2. 设置全局变量(如果需要)
// kieSession.setGlobal("logger", LoggerFactory.getLogger(this.getClass()));
// kieSession.setGlobal("someService", someService);
// 3. 插入事实(Fact)到工作内存(Working Memory)
kieSession.insert(order);
// 如果订单中有用户对象,也需要插入。但通常Order引用User,只插入Order即可。
// 如果规则中需要单独匹配User,最好也插入。
if (order.getUser() != null) {
kieSession.insert(order.getUser());
}
// 4. 触发所有匹配的规则
int firedRulesCount = kieSession.fireAllRules();
System.out.println("触发了 " + firedRulesCount + " 条规则");
// 5. 规则执行完毕后,订单对象已经被规则修改(添加了优惠券)
return order;
} finally {
// 6. 非常重要!销毁KieSession,释放资源。
// 如果不销毁,可能会导致内存泄漏,特别是Session中缓存了大量Fact时。
kieSession.dispose();
}
}
}
关键点解析:
-
KieContainer: 由Spring管理,单例。它负责加载、编译和管理定义在/rules目录下的所有规则文件。我们通过@Autowired注入它。 -
KieSession: 每次执行都需要创建新的 。因为它内部维护了本次规则执行的工作内存(插入的Facts)和激活的规则议程(Agenda)。如果复用,会导致不同请求间的Facts混乱。这是新手常犯的错误。 -
kieSession.insert(fact): 将业务对象插入到规则引擎的工作内存中。只有插入的对象,才会被规则的条件部分(when)进行模式匹配。你可以插入多个不同类型的Fact。 -
kieSession.fireAllRules(): 触发引擎开始工作。引擎会检查工作内存中的所有Fact,匹配所有规则的LHS,将符合条件的规则放入“议程”(Agenda),然后根据优先级(salience)等策略依次执行这些规则的RHS。该方法返回本次触发执行的规则总数。 -
kieSession.dispose(): 必须放在finally块中调用! 用于释放KieSession占用的资源。特别是在生产环境,规则复杂、Fact多的情况下,不释放会导致严重的内存泄漏。
4.2 创建Controller进行测试
让我们创建一个简单的REST接口来测试整个流程。
package com.example.droolsdemo.controller;
import com.example.droolsdemo.model.Order;
import com.example.droolsdemo.model.User;
import com.example.droolsdemo.service.RuleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import java.math.BigDecimal;
@RestController
public class DiscountController {
@Autowired
private RuleService ruleService;
@PostMapping("/calculate")
public Order calculateDiscount(@RequestBody Order orderInput) {
// 在实际应用中,orderInput可能来自前端,这里我们简单处理
// 确保订单有用户信息
if (orderInput.getUser() == null) {
throw new RuntimeException("用户信息不能为空");
}
// 调用规则服务
Order resultOrder = ruleService.executeDiscountRule(orderInput);
// 计算最终支付金额
resultOrder.setPayAmount(resultOrder.calculateFinalAmount());
return resultOrder;
}
// 一个快速测试的GET接口
@GetMapping("/test")
public Order testDiscount() {
User user = new User();
user.setId(1L);
user.setLevel("VIP");
user.setIsNew(true); // 既是VIP又是新用户
user.setTotalConsumption(new BigDecimal("500"));
Order order = new Order();
order.setOrderNo("TEST20231027001");
order.setUser(user);
order.setOriginalAmount(new BigDecimal("250"));
Order result = ruleService.executeDiscountRule(order);
result.setPayAmount(result.calculateFinalAmount());
System.out.println("订单原始金额: " + order.getOriginalAmount());
System.out.println("应用优惠券: ");
result.getCoupons().forEach(c -> System.out.println(" - " + c.getName() + ": 减" + c.getDiscountAmount() + "元"));
System.out.println("最终支付金额: " + result.getPayAmount());
return result;
}
}
启动Spring Boot应用,访问
http://localhost:8080/test
,你会在控制台看到类似输出:
[规则触发] 新用户首单立减10元,订单号: TEST20231027001
[规则触发] VIP用户满100减15,订单号: TEST20231027001
[规则触发] 订单满200享95折,订单号: TEST20231027001
触发了 3 条规则
订单原始金额: 250
应用优惠券:
- 新用户首单礼: 减10元
- VIP满100减15: 减15元
- 通用95折券: 减12.5元
最终支付金额: 212.5
成功了!规则引擎自动匹配了三条规则,并为订单添加了相应的优惠券。最终支付金额 = 250 - 10 - 15 - 12.5 = 212.5元。
5. 规则冲突与执行顺序:深入理解Salience与Agenda-Group
上面的例子中,三条规则互不干扰,全部触发。但实际业务中,规则之间往往存在冲突或依赖。比如,“新用户专享券”和“全平台通用券”可能不能叠加。这就需要我们控制规则的执行顺序和激活条件。
5.1 Salience(优先级)的陷阱与正确用法
salience
属性决定了规则在
议程(Agenda)中的执行顺序
,数值大的先执行。但这
不意味着
数值大的规则会阻止数值小的规则被激活。只要事实匹配,所有规则都会被放入议程,只是执行顺序不同。
假设我们修改规则,让“新用户礼”和“VIP专享券”互斥(只能二选一)。一种
错误
的写法是只设置不同的
salience
:
rule “New User Exclusive” salience 10 ...
rule “VIP Exclusive” salience 9 ...
即使“新用户规则”先执行,它给订单加了券,但“VIP规则”的条件依然满足(用户是VIP,订单金额达标),它仍然会执行,导致两张券都被加上。
正确的互斥逻辑需要在规则中显式控制。 常见方法有:
方法一:在RHS中修改Fact,使其他规则条件失效。
rule “New User Exclusive”
salience 10
when
$o: Order($u: user)
$u: User(isNew == true)
not(Coupon(name == “新用户专享”))
then
// ... 添加新用户专享券
$o.setUserTag(“NEW_USER_USED”); // 给用户或订单打上一个标记
update($o);
end
rule “VIP Exclusive”
salience 9
when
$o: Order($u: user, userTag != “NEW_USER_USED”) // 检查标记,如果已用新用户券,则本规则不匹配
$u: User(level == “VIP”)
then
// ... 添加VIP专享券
end
方法二:使用
activation-group
(激活组)。
同组内的规则,只有一个会被执行(最先被放入议程的那个)。
rule “New User Exclusive”
activation-group “exclusive-discount” // 属于同一个激活组
salience 10 // 在组内,salience仍然决定谁先进入议程
when ...
then ...
end
rule “VIP Exclusive”
activation-group “exclusive-discount”
salience 9
when ...
then ...
end
当“新用户规则”执行后,同组内的“VIP规则”会自动从议程中移除,不会执行。
经验之谈 :
salience更适合用于定义规则的“阶段”或“层次”。例如,先把所有“资格校验”规则(salience=100)执行完,再执行“计算折扣”规则(salience=50),最后执行“结果校验”规则(salience=0)。用它来做细粒度的互斥控制,很容易出错,代码可读性也差。activation-group是更明确的互斥声明。
5.2 Agenda-Group(议程组)与显式焦点控制
agenda-group
用于将规则分组。默认情况下,所有规则的agenda-group都是
“MAIN”
,并且该组拥有焦点(focus),所以所有规则都有机会执行。
你可以通过设置
agenda-group
属性,并将某组设置为焦点,来
分阶段、按需执行
规则。这在复杂的规则流中非常有用。
// 阶段一:资格校验
rule “Check User Status”
agenda-group “validation”
salience 100
when
$o: Order($u: user)
$u: User(isNew == null) // 用户状态未知
then
// 可能是从数据库加载用户状态
// userService.refreshUserStatus($u);
System.out.println(“用户状态需校验”);
// 校验失败可以中断流程?通常通过设置一个标志位
$o.setValidationPassed(false);
update($o);
end
rule “Check Order Amount”
agenda-group “validation”
salience 90
when
$o: Order(originalAmount <= 0)
then
System.out.println(“订单金额无效”);
$o.setValidationPassed(false);
update($o);
end
rule “Validation Passed”
agenda-group “validation”
salience 80
when
$o: Order(validationPassed != false) // 默认是null或true
then
System.out.println(“资格校验通过,进入折扣计算阶段”);
// 触发下一阶段
drools.setFocus(“discount-calculation”);
end
// 阶段二:折扣计算
rule “Calculate Discount”
agenda-group “discount-calculation”
when
$o: Order(validationPassed == true, originalAmount > 100)
then
// ... 计算折扣
System.out.println(“计算折扣”);
end
在Java代码中,你需要先为指定的
agenda-group
设置焦点,规则才会执行:
KieSession kieSession = ...;
kieSession.getAgenda().getAgendaGroup(“validation”).setFocus(); // 先聚焦到校验组
kieSession.insert(order);
kieSession.fireAllRules(); // 只会执行“validation”组内有焦点的规则,以及后续通过setFocus触发的组
agenda-group
提供了更强大的流程控制能力,适合将庞大的规则集按业务阶段进行划分。
6. 高级特性与生产实践建议
掌握了基础整合和规则编写后,我们来看看一些能提升效率和维护性的高级特性和实践。
6.1 使用决策表(Decision Table)管理规则
对于大量相似、结构化的规则(比如不同用户等级、不同商品品类对应不同折扣率),用DRL一条条写非常繁琐。Drools支持Excel格式的决策表。
-
创建一个Excel文件(如
discount-rules.xlsx)。 - 使用特定的模板格式。通常第一行是RuleSet、Import等配置,第二行是条件/动作的列标识(CONDITION, ACTION),第三行是对象/属性,第四行开始是具体的规则数据。
-
将文件放在
src/main/resources/rules目录下,后缀可以是.xls或.xlsx。 - Drools会自动识别并加载。
决策表的优势是业务人员可以直接在Excel中维护规则,修改后上传即可生效(结合动态加载)。但它的缺点是逻辑表达能力不如DRL,复杂的条件判断还是需要DRL。
6.2 规则动态加载与热更新
在生产环境中,业务规则需要频繁调整。我们不可能每次改规则都重启服务。Drools提供了
KieScanner
支持动态加载。
在Spring Boot中,可以通过配置
releaseId
并启用
KieScanner
来监控Maven仓库中的规则jar包更新。但更常见的做法是
将规则文件存储在数据库或配置中心(如Apollo, Nacos)
。
实现思路:
-
自定义一个
KieFileSystem实现,不从classpath读取文件,而是从数据库/配置中心获取DRL内容字符串。 -
将获取到的内容通过
KieFileSystem.write(“src/main/resources/rules/from-db.drl”, drlContentString)写入虚拟文件系统。 -
使用
KieBuilder构建KieModule,更新KieContainer。 - 通过监听配置变更事件(如数据库binlog、配置中心推送)来触发重新加载。
这个过程相对复杂,需要处理好线程安全(更新
KieContainer
时暂停查询)和版本回滚。社区有一些开源项目(如
drools-spring-integration
的更高级用法)提供了参考。
6.3 性能监控与调试
当规则数量庞大时,性能监控至关重要。
-
KieSession监听器 : 可以添加RuleRuntimeEventListener和AgendaEventListener来监听Fact的插入/更新/删除和规则的匹配/执行/取消事件,用于调试和记录审计日志。kieSession.addEventListener(new DebugAgendaEventListener()); kieSession.addEventListener(new DebugRuleRuntimeEventListener()); -
KieContainer的KieBase:KieContainer.getKieBase()获取的KieBase是规则编译后的静态知识库,创建KieSession开销较小。但规则热更新会导致KieBase重建。 -
避免在LHS中调用代价高的方法
: 规则条件中的表达式会被频繁求值。例如
User(getLevelFromRemoteService() == “VIP”)这种调用远程服务的方法,会带来巨大的性能开销。正确的做法是将所需数据预先加载到Fact的属性中。
6.4 一个完整的、带异常处理的Service示例
最后,给出一个更健壮的
RuleService
示例,包含异常处理和资源清理。
@Service
@Slf4j // 使用Lombok的日志注解
public class RobustRuleService {
@Autowired
private KieContainer kieContainer;
public Order executeRulesSafely(Order order) {
KieSession kieSession = null;
long startTime = System.currentTimeMillis();
try {
kieSession = kieContainer.newKieSession();
// 可以添加监听器用于审计
// kieSession.addEventListener(new AuditLoggingEventListener());
kieSession.insert(order);
if (order.getUser() != null) {
kieSession.insert(order.getUser());
}
int fired = kieSession.fireAllRules();
log.info(“规则执行完毕。订单[{}]触发{}条规则,耗时{}ms”, order.getOrderNo(), fired, System.currentTimeMillis() - startTime);
return order;
} catch (Exception e) {
log.error(“执行规则引擎时发生异常,订单号: {}”, order.getOrderNo(), e);
// 根据业务需求决定是抛出异常,还是返回一个默认结果(如不应用任何优惠的订单)
throw new BusinessException(“规则计算失败”, e);
} finally {
if (kieSession != null) {
try {
kieSession.dispose();
} catch (Exception e) {
log.warn(“销毁KieSession时发生异常”, e);
}
}
}
}
}
整合Drools到Spring Boot项目,核心在于理解“规则与代码分离”的思想,并处理好
KieSession
的生命周期、规则冲突以及生产环境下的动态更新需求。从简单的优惠计算到复杂的风控流程,Drools都能提供清晰、可维护的解决方案。开始动手吧,把你的业务逻辑从错综复杂的
if-else
中解放出来。

441

被折叠的 条评论
为什么被折叠?



