规则引擎
Drools规则加载方式
- 规则文件.drl加载
- excel决策表中加载
- 构造一个字符串加载
Drools规则引擎
快速入门
第一步:创建maven工程drools_quickstart并导入drools相关maven坐标
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-compiler</artifactId>
<version>7.10.0.Final</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
添加pom依赖
第二步:根据drools要求创建resources/META-INF/kmodule.xml配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<kmodule xmlns="http://www.drools.org/xsd/kmodule">
<!--
name:指定kbase的名称,可以任意,但是需要唯一
packages:指定规则文件的目录,需要根据实际情况填写,否则无法加载到规则文件
default:指定当前kbase是否为默认
-->
<kbase name="myKbase1" packages="rules" default="true">
<!--
name:指定ksession名称,可以任意,但是需要唯一
default:指定当前session是否为默认
-->
<ksession name="ksession-rule" default="true"/>
</kbase>
</kmodule>
注意:上面配置文件的名称和位置都是固定写法,不能更改
第三步:创建实体类Order
package com.itheima.drools.entity;
/**
* 订单
*/
public class Order {
private Double originalPrice;//订单原始价格,即优惠前价格
private Double realPrice;//订单真实价格,即优惠后价格
public String toString() {
return "Order{" +
"originalPrice=" + originalPrice +
", realPrice=" + realPrice +
'}';
}
public Double getOriginalPrice() {
return originalPrice;
}
public void setOriginalPrice(Double originalPrice) {
this.originalPrice = originalPrice;
}
public Double getRealPrice() {
return realPrice;
}
public void setRealPrice(Double realPrice) {
this.realPrice = realPrice;
}
}
第四步:创建规则文件resources/rules/bookDiscount.drl
规则场景 :
100元以下没有优惠;
100元以上200元以下优惠20元
200元以上(包含)300元以下(包含)优惠50元
300元以上优惠100元
//图书优惠规则
package book.discount
import com.itheima.drools.entity.Order
//规则一:所购图书总价在100元以下的没有优惠
rule "book_discount_1" //需要唯一
when //后面跟条件
$order:Order(originalPrice < 100)
then //符合上面条件则执行后面操作
$order.setRealPrice($order.getOriginalPrice());
System.out.println("成功匹配到规则一:所购图书总价在100元以下的没有优惠");
end //一个规则结束
//规则二:所购图书总价在100到200元的优惠20元
rule "book_discount_2"
when
$order:Order(originalPrice < 200 && originalPrice >= 100)
then
$order.setRealPrice($order.getOriginalPrice() - 20);
System.out.println("成功匹配到规则二:所购图书总价在100到200元的优惠20元");
end
//规则三:所购图书总价在200到300元的优惠50元
rule "book_discount_3"
when
$order:Order(originalPrice <= 300 && originalPrice >= 200)
then
$order.setRealPrice($order.getOriginalPrice() - 50);
System.out.println("成功匹配到规则三:所购图书总价在200到300元的优惠50元");
end
//规则四:所购图书总价在300元以上的优惠100元
rule "book_discount_4"
when
$order:Order(originalPrice > 300)
then
$order.setRealPrice($order.getOriginalPrice() - 100);
System.out.println("成功匹配到规则四:所购图书总价在300元以上的优惠100元");
end
第五步:编写单元测试
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iA72OSXK-1684140681032)(项目数据管理.assets/image-20230330094723718.png)]
@Test
public void test1(){
//第一步 :获取服务
KieServices kieServices = KieServices.Factory.get();
//第二步:通过服务获取容器
KieContainer kieClasspathContainer = kieServices.getKieClasspathContainer();
//第三步: 通过容器获取kieSession,由KieSession去和规则引擎交互(会话对象,用于和规则引擎交互)
KieSession kieSession = kieClasspathContainer.newKieSession();
//构造订单对象(事实对象),设置原始价格,由规则引擎根据优惠规则计算优惠后的价格
Order order = new Order();
order.setOriginalPrice(210D);
//第四步 :将数据提供给规则引擎,规则引擎会根据提供的数据进行规则匹配
kieSession.insert(order);
//第五步: 激活规则引擎,如果规则匹配成功则执行规则
kieSession.fireAllRules();
//第六步: 闭会话
kieSession.dispose();
System.out.println("优惠前原始价格:" + order.getOriginalPrice() +
",优惠后价格:" + order.getRealPrice());
}
springboot集成Drools
第一步 :添加maven依赖
<!--drools规则引擎-->
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-core</artifactId>
<version>7.6.0.Final</version>
</dependency>
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-compiler</artifactId>
<version>7.6.0.Final</version>
</dependency>
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-templates</artifactId>
<version>7.6.0.Final</version>
</dependency>
<dependency>
<groupId>org.kie</groupId>
<artifactId>kie-api</artifactId>
<version>7.6.0.Final</version>
</dependency>
<dependency>
<groupId>org.kie</groupId>
<artifactId>kie-spring</artifactId>
<version>7.6.0.Final</version>
</dependency>
第二步 :创建Drools核心配置, 对应的是快速入门中的(xml文件+第五步单元测试中可复用的代码)
@Configuration
public class DroolsConfig
{
//规则文件的位置
private static final String RULES_PATH = "rules/";
@Bean
@ConditionalOnMissingBean(KieFileSystem.class)
public KieFileSystem kieFileSystem() throws IOException
{
KieFileSystem kieFileSystem = getKieServices().newKieFileSystem();
for (Resource file : getRuleFiles())
{
kieFileSystem.write(ResourceFactory.newClassPathResource(RULES_PATH
+ file.getFilename(), "UTF-8"));
}
return kieFileSystem;
}
private Resource[] getRuleFiles() throws IOException
{
ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
return resourcePatternResolver.getResources("classpath*:" + RULES_PATH
+ "**/*.*");
}
@Bean
@ConditionalOnMissingBean(KieContainer.class)
public KieContainer kieContainer() throws IOException
{
final KieRepository kieRepository = getKieServices().getRepository();
//设置时间格式
System.setProperty("drools.dateformat","yyyy-MM-dd HH:mm");
kieRepository.addKieModule(kieRepository::getDefaultReleaseId);
KieBuilder kieBuilder = getKieServices().newKieBuilder(kieFileSystem());
kieBuilder.buildAll();
return getKieServices().newKieContainer(
kieRepository.getDefaultReleaseId());
}
private KieServices getKieServices()
{
return KieServices.Factory.get();
}
@Bean
@ConditionalOnMissingBean(KieBase.class)
public KieBase kieBase() throws IOException
{
return kieContainer().getKieBase();
}
@Bean
@ConditionalOnMissingBean(KieSession.class)
public KieSession kieSession() throws IOException
{
return kieContainer().newKieSession();
}
@Bean @ConditionalOnMissingBean(KModuleBeanFactoryPostProcessor.class)
public KModuleBeanFactoryPostProcessor kiePostProcessor()
{
return new KModuleBeanFactoryPostProcessor();
}
}
第三步 :创建实体类
package com.itheima.drools.entity;
/**
* 订单
*/
public class Order {
private Double originalPrice;//订单原始价格,即优惠前价格
private Double realPrice;//订单真实价格,即优惠后价格
public String toString() {
return "Order{" +
"originalPrice=" + originalPrice +
", realPrice=" + realPrice +
'}';
}
public Double getOriginalPrice() {
return originalPrice;
}
public void setOriginalPrice(Double originalPrice) {
this.originalPrice = originalPrice;
}
public Double getRealPrice() {
return realPrice;
}
public void setRealPrice(Double realPrice) {
this.realPrice = realPrice;
}
}
第四步 :创建规则文件(xxx.drl)
//图书优惠规则
package rules
import com.itheima.drools.entity.Order
//规则一:所购图书总价在100元以下的没有优惠
rule "book_discount_1" //需要唯一
when //后面跟条件
$order:Order(originalPrice < 100)
then //符合上面条件则执行后面操作
$order.setRealPrice($order.getOriginalPrice());
System.out.println("成功匹配到规则一:所购图书总价在100元以下的没有优惠");
end //一个规则结束
//规则二:所购图书总价在100到200元的优惠20元
rule "book_discount_2"
when
$order:Order(originalPrice < 200 && originalPrice >= 100)
then
$order.setRealPrice($order.getOriginalPrice() - 20);
System.out.println("成功匹配到规则二:所购图书总价在100到200元的优惠20元");
end
//规则三:所购图书总价在200到300元的优惠50元
rule "book_discount_3"
when
$order:Order(originalPrice <= 300 && originalPrice >= 200)
then
$order.setRealPrice($order.getOriginalPrice() - 50);
System.out.println("成功匹配到规则三:所购图书总价在200到300元的优惠50元");
end
//规则四:所购图书总价在300元以上的优惠100元
rule "book_discount_4"
when
$order:Order(originalPrice > 300)
then
$order.setRealPrice($order.getOriginalPrice() - 100);
System.out.println("成功匹配到规则四:所购图书总价在300元以上的优惠100元");
end
第五步:编写Service层
@Service
public class DroolsServicesImpl {
@Autowired
private KieBase kieBase;
public Order droolsTest(Order order){
KieSession session = kieBase.newKieSession();
session.insert(order);
session.fireAllRules();
session.dispose();
return order;
}
}
Drools语法 :
rule "book_discount_1"
activation-group "A" //activation-group :分组,当条件在相同组中都被满足, 也只会执行一个.这里会输出A组中的规则一和B组中的规则3(先头后尾的选择方式)
salience 1 //优先级设置,数字越高,则优先级越高, 和activation-group配合,可以提高尾部的优先级, 从而控制优先执行尾部的判断,而不是头部
when
$s:Order(originalPrice < 100)
then
$s.setRealPrice($s.getOriginalPrice());
System.out.println("成功匹配到规则一:所购图书总价在100元以下的没有优惠");
end
//规则二
rule "book_discount_2"
activation-group "A"
when
$s:Order(originalPrice < 100)
then
$s.setRealPrice($s.getOriginalPrice() - 20);
System.out.println("成功匹配到规则二:所购图书总价在100到200元的优惠20元");
end
//规则三
rule "book_discount_3"
activation-group "B"
when
$s:Order(originalPrice < 100)
then
$s.setRealPrice($s.getOriginalPrice() - 50);
System.out.println("成功匹配到规则三:所购图书总价在200到300元的优惠50元");
end
rule "book_discount_4"
when
$s:Order(originalPrice > 300)
then
$s.setRealPrice($s.getOriginalPrice() - 100);
System.out.println("成功匹配到规则四:所购图书总价在300元以上的优惠100元");
end
activation-group “A”
- activation-group :分组,当条件在相同组中都被满足, 也只会执行一个.这里会输出A组中的规则一和B组中的规则3(先头后尾的选择方式)
salience 1
- 优先级设置,数字越高,则优先级越高, 和activation-group配合,可以提高尾部的优先级, 从而控制优先执行尾部的判断,而不是头部
no-loop true
lock-on-active true
insert
update
retract | delete
问题 :
当多份规则文件如何加载?
目前业务使用的方式是将规则文件变为字符串的方式存在数据库中, 每次程序执行就将所有规则文件加载一遍,加载到KieBase中.(这里就需要注意关于规则文件的热加载问题)
关于热加载遇到的问题记录 :
当使用drools进行规则判断时, 如果使用的KieSession对象是全局对象(就是应用启动时就创建的KieSession对象), 当使用session.insert(object)进行规则判断时(这里是假代码, session是KieSession这个对象, object是要进行规则判断的);会将object对象缓存到session中, 如果判断成功还好, 判断失败的话.会在下次判断成功时会被一起通过.所以这里一定要注意写个session.delete(FactHandle);将缓存删除.
[注 :这里的FactHandle可以通过上面的session.insert(object)的返回值获得]
热加载使用到的api
查询指定的kieBase对象中存在的规则条例名称及包名
for (KiePackage kiePackage : kieBase.getKiePackages();) { for (Rule rule : kiePackage.getRules()) { log.info("rule name {}",rule.getName()); log.info("rule package {}",rule.getPackageName()); } }
加载字符串规则文件 :
KieHelper hel = new KieHelper(); hel.addContent(drl,Resource.DRL); //将规则文件放入其中并设置文件类型 Results res = kieHelper.verify(); // Results.getMessage()方法可以获得返回信息来判断是否添加成功 KieSession kie = kieHelper.build().newKieSession(); //获得加载了规则文件的session
向已经创建的kieSession对象中添加/修改/删除规则文件 :
// 先将要添加的文件放入Resource对象中 Resource resource = ResourceFactory.newFileResource(drl); // 新增或者更新 KnowledgeBuilder kBuilder = KnowledgeBuilderFactory.newKnowledgeBuilder(); kBuilder.add(resource, ResourceType.DRL); // 创建KieContainer对象(我这边使用的是工具类) KieContainer con = KieSessionUtil.getKieContainer(); InternalKnowledgeBase knowledgeBase = (InternalKnowledgeBase) con.getKieBase(kieBaseName); //旧的同package名、同rule名会覆盖,不同package名同rule名不会覆盖;即不同package可以同rule名。 knowledgeBase.addPackages(kBuilder.getKnowledgePackages()); //删除 //knowledgeBase.removeKiePackage("packageName"); //knowledgeBase.removeObjectsGeneratedFromResource(resource);
结束语 :
后续还会增加cloudevent这个技术的笔记.后续如果有什么问题,有什么不足的地方,或我的笔记中有什么错误;欢迎各位大佬能提点几句. 万分感谢.如果有什么你们觉得好用的技术和框架, 如果可以的话能否在评论区中留下言. 后续我整理好也会将笔记发布上来. 希望大家能互相学习.也感谢无偿提供知识的大佬们.