Drools规则引擎学习
通过尚硅谷的乐尚代驾项目,接触到了Drools规则引擎
代驾费用=里程费 + 等候费 + 远途费
规则虽然是上面这样的,但是实际中,规则可能进行随时调整,比如油价上涨收取燃油附加费,比如大雪天气,费用增加等等
对于不经常变化的业务,我们通常是硬编码到程序中。但是经常变化的业务,我们就得把业务流程从代码中剥离出来,我们怎么从程序中剥离出去?这里就需要用到规则引擎了。
1 规则引擎概述
规则引擎核心思想:将应用程序中的业务决策部分分离出来
使得业务规则的变更不需要修改项目代码、重启服务器就可以在线上环境立即生效
2 Drools入门案例
第一步 创建SpringBoot工程
第二步 引入Drools相关依赖
<properties>
<java.version>17</java.version>
<drools.version>8.41.0.Final</drools.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-core</artifactId>
<version>${drools.version}</version>
</dependency>
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-compiler</artifactId>
<version>${drools.version}</version>
</dependency>
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-decisiontables</artifactId>
<version>${drools.version}</version>
</dependency>
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-mvel</artifactId>
<version>${drools.version}</version>
</dependency>
</dependencies>
第三步 创建Drools配置类
/**
* 规则引擎配置类
*/
@Configuration
public class DroolsConfig {
private static final KieServices kieServices = KieServices.Factory.get();
//制定规则文件的路径
private static final String RULES_CUSTOMER_RULES_DRL = "rules/order.drl";
@Bean
public KieContainer kieContainer() {
//获得Kie容器对象
KieFileSystem kieFileSystem = kieServices.newKieFileSystem();
kieFileSystem.write(ResourceFactory.newClassPathResource(RULES_CUSTOMER_RULES_DRL));
KieBuilder kieBuilder = kieServices.newKieBuilder(kieFileSystem);
kieBuilder.buildAll();
KieModule kieModule = kieBuilder.getKieModule();
KieContainer kieContainer = kieServices.newKieContainer(kieModule.getReleaseId());
return kieContainer;
}
}
第四步 创建实体类
public class Order {
private double amout;
private double score;
public double getAmout() {
return amout;
}
public void setAmout(double amout) {
this.amout = amout;
}
public double getScore() {
return score;
}
public void setScore(double score) {
this.score = score;
}
}
第五步 创建规则文件
//订单积分规则
package com.atguigu.drools.order
import com.atguigu.drools.bean.Order
//规则一:100元以下 不加分
rule "order_rule_1"
when
$order:Order(amout < 100)
then
$order.setScore(0);
System.out.println("成功匹配到规则一:100元以下 不加分");
end
//规则二:100元 - 500元 加100分
rule "order_rule_2"
when
$order:Order(amout >= 100 && amout < 500)
then
$order.setScore(100);
System.out.println("成功匹配到规则二:100元 - 500元 加100分");
end
//规则三:500元 - 1000元 加500分
rule "order_rule_3"
when
$order:Order(amout >= 500 && amout < 1000)
then
$order.setScore(500);
System.out.println("成功匹配到规则三:500元 - 1000元 加500分");
end
//规则四:1000元以上 加1000分
rule "order_rule_4"
when
$order:Order(amout >= 1000)
then
$order.setScore(1000);
System.out.println("成功匹配到规则四:1000元以上 加1000分");
end
第六步 测试
@SpringBootTest
public class DroolsDemosApplicationTests {
@Autowired
private KieContainer kieContainer;
@Test
public void test(){
//从Kie容器对象中获取会话对象
KieSession session = kieContainer.newKieSession();
//Fact对象,事实对象
Order order = new Order();
order.setAmout(300);
//将Order对象插入到工作内存中
session.insert(order);
//激活规则,由Drools框架自动进行规则匹配,如果规则匹配成功,则执行当前规则
session.fireAllRules();
//关闭会话
session.dispose();
System.out.println("订单金额:" + order.getAmout() +
",添加积分:" + order.getScore());
}
}
规则引擎组成和执行流程
drools规则引擎由以下三部分构成:
- Working Memory(工作内存)
- Rule Base(规则库)
- Inference Engine(推理引擎)
其中Inference Engine(推理引擎)又包括:
- Pattern Matcher(匹配器) 具体匹配哪一个规则,由这个完成
- Agenda(议程)
- Execution Engine(执行引擎)
3 Drools基础语法
3.1 规则文件组成
- 在使用Drools时非常重要的一个工作就是编写规则文件,通常规则文件的后缀为.drl
3.2 规则体语法结构
规则体语法结构 开始位置 rule,结束位置 end
结构组成:
rule "ruleName"
attributes
when
LHS
then
RHS
end
rule:关键字,表示规则开始,参数为规则的唯一名称。
attributes:规则属性,是rule与when之间的参数,为可选项。
when:关键字,后面跟规则的条件部分。
LHS(Left Hand Side):是规则的条件部分的通用名称。它由零个或多个条件元素组成。如果LHS为空,则它将被视为始终为true的条件元素。 (左手边)
then:关键字,后面跟规则的结果部分。
RHS(Right Hand Side):是规则的后果或行动部分的通用名称。 (右手边)
end:关键字,表示一个规则结束。
3.3 模式匹配
rule "rule_1"
when
$order:Order(amout >100 && amout < 500)
then
$order.setScore(100);
System.out.println("成功匹配到规则二:100元 - 500元 加100分");
end
3.4 Drools内置方法
Drools提供了一些方法可以用来操作工作内存中的数据,操作完成后规则引擎会重新进行相关规则的匹配,原来没有匹配成功的规则在我们修改数据完成后有可能就会匹配成功了。
修改 update方法
update方法的作用是更新工作内存中的数据,并让相关的规则重新匹配。
参数:
//Fact对象,事实对象
Order order = new Order();
order.setAmout(30);
规则:
rule "order_rule_1"
when
$order:Order(amout < 100)
then
$order.setAmout(150);
update($order)
System.out.println("成功匹配到规则一:100元以下 不加分");
end
rule "order_rule_2"
when
$order:Order(amout >= 100 && amout < 500)
then
$order.setScore(100);
System.out.println("成功匹配到规则二:100元 - 500元 加100分");
end
- 添加 insert方法
insert方法的作用是向工作内存中插入数据,并让相关的规则重新匹配
rule "order_rule_1"
when
$order:Order(amout < 100)
then
Order order = new Order();
order.setAmout(150);
insert(order);
System.out.println("成功匹配到规则一:100元以下 不加分");
end
rule "order_rule_2"
when
$order:Order(amout >= 100 && amout < 500)
then
$order.setScore(100);
System.out.println("成功匹配到规则二:100元 - 500元 加100分");
end
- 删除 retract方法
retract方法的作用是删除工作内存中的数据,并让相关的规则重新匹配。
//规则一:100元以下 不加分
rule "order_rule_1"
when
$order:Order(amout < 100)
then
retract($order) //retract方法的作用是删除工作内存中的Fact对象,会导致相关规则重新匹配
System.out.println("成功匹配到规则一:100元以下 不加分");
end
4 Drools规则属性
4.1 salience属性
- salience属性用于指定规则的执行优先级,取值类型为Integer。数值越大越优先执行
- 没有添加执行优先级,执行由上到下执行
package com.order
rule "rule_1"
when
eval(true)
then
System.out.println("规则rule_1触发");
end
rule "rule_2"
when
eval(true)
then
System.out.println("规则rule_2触发");
end
rule "rule_3"
when
eval(true)
then
System.out.println("规则rule_3触发");
end
- 添加优先级
package com.order
rule "rule_1"
salience 9
when
eval(true)
then
System.out.println("规则rule_1触发");
end
rule "rule_2"
salience 10
when
eval(true)
then
System.out.println("规则rule_2触发");
end
rule "rule_3"
salience 8
when
eval(true)
then
System.out.println("规则rule_3触发");
end
4.2 no-loop属性
- no-loop属性用于防止死循环
//订单积分规则
package com.order
import com.atguigu.drools.model.Order
//规则一:100元以下 不加分
rule "order_rule_1"
no-loop true //防止陷入死循环
when
$order:Order(amout < 100)
then
$order.setScore(0);
update($order)
System.out.println("成功匹配到规则一:100元以下 不加分");
end
5 Drools高级语法
global关键字用于在规则文件中定义全局变量,它可以让应用程序的对象在规则文件中能够被访问。这种应该是只传进来了order,没传进来integral。所以用全局变量。
语法结构为:global 对象类型 对象名称
//订单积分规则
package com.order
import com.atguigu.drools.bean.Order1
global com.atguigu.drools.bean.Integral integral;
//规则一:100元以下 不加分
rule "order_rule_1"
no-loop true //防止陷入死循环
when
$order:Order1(amout < 100)
then
integral.setScore(10);
update($order)
System.out.println("成功匹配到规则一:100元以下 不加分");
end
测试
因为在第三步已经有配置文件,且有@Bean,所以kieContainer可以直接注入,对应于某个特定的规则.ftl
@SpringBootTest
public class DroolsDemosApplicationTests {
@Autowired
private KieContainer kieContainer;
@Test
public void test(){
//从Kie容器对象中获取会话对象
KieSession session = kieContainer.newKieSession();
//Fact对象,事实对象
Order1 order = new Order1();
order.setAmout(30);
//全局变量
Integral integral = new Integral();
session.setGlobal("integral",integral);
//将Order对象插入到工作内存中
session.insert(order);
//激活规则,由Drools框架自动进行规则匹配,如果规则匹配成功,则执行当前规则
session.fireAllRules();
//关闭会话
session.dispose();
System.out.println("订单金额:" + order.getAmout() +
",添加积分:" + integral.getScore());
}
}
实战
在核心业务代码中
@Slf4j
@Service
@SuppressWarnings({"unchecked", "rawtypes"})
public class FeeRuleServiceImpl implements FeeRuleService {
@Autowired
private KieContainer kieContainer;
//计算订单费用
@Override
public FeeRuleResponseVo calculateOrderFee(FeeRuleRequestForm calculateOrderFeeForm) {
//封装输入对象
FeeRuleRequest feeRuleRequest = new FeeRuleRequest();
feeRuleRequest.setDistance(calculateOrderFeeForm.getDistance());
Date startTime = calculateOrderFeeForm.getStartTime();
feeRuleRequest.setStartTime(new DateTime(startTime).toString("HH:mm:ss"));
feeRuleRequest.setWaitMinute(calculateOrderFeeForm.getWaitMinute());
//Drools使用
KieSession kieSession = kieContainer.newKieSession();
//封装返回对象
FeeRuleResponse feeRuleResponse = new FeeRuleResponse();
kieSession.setGlobal("feeRuleResponse",feeRuleResponse);
kieSession.insert(feeRuleRequest);
kieSession.fireAllRules();
kieSession.dispose();
//封装数据到FeeRuleResponseVo返回
FeeRuleResponseVo feeRuleResponseVo = new FeeRuleResponseVo();
// feeRuleResponse -- feeRuleResponseVo
BeanUtils.copyProperties(feeRuleResponse,feeRuleResponseVo);
return feeRuleResponseVo;
}
}
其中kieContainer是注入的,FeeRuleRequest和FeeRuleRequestForm里面内容基本一致
@Data
public class FeeRuleRequest {
@Schema(description = "代驾里程")
private BigDecimal distance;
@Schema(description = "代驾时间")
private String startTime;
@Schema(description = "等候分钟")
private Integer waitMinute;
}
业务对应的controller层
@Slf4j
@RestController
@RequestMapping("/rules/fee")
@SuppressWarnings({"unchecked", "rawtypes"})
public class FeeRuleController {
@Autowired
private FeeRuleService feeRuleService;
@Operation(summary = "计算订单费用")
@PostMapping("/calculateOrderFee")
public Result<FeeRuleResponseVo> calculateOrderFee(@RequestBody FeeRuleRequestForm calculateOrderFeeForm) {
FeeRuleResponseVo feeRuleResponseVo = feeRuleService.calculateOrderFee(calculateOrderFeeForm);
return Result.ok(feeRuleResponseVo);
}
}
业务代码中将feeResponse设为全局变量。因为他是要返回的结果。
package com.atguigu.daijia
import com.atguigu.daijia.model.form.rules.FeeRuleRequest;
import java.math.BigDecimal;
import java.math.RoundingMode;
global com.atguigu.daijia.model.vo.rules.FeeRuleResponse feeRuleResponse;
具体规则...里面再对feeRuleResponse进行set