一、背景

最近在学习规则引擎drools,此处简单记录一下drools的入门案例。

二、为什么要学习drools

假设我们存在如下场景:

在我们到商店购买衣服的时候,经常会发生这样的事情,购买1件不打折,购买2件打0.98折,购买3件级以上打0.85折。

那么我们在代码中如果要实现上述功能,是不是就需要编写if ... else语句,假设后期规则变了,是不是就需要修改这些if ... else语句,然后程序重新部署。这样是可以实现,但是不够优雅。那么我们是否可以将这些业务规则写入到规则文件中,以后规则变更直接修改规则文件即可?而drools就可以实现这个功能。

三、实现上方这个简单的打折案例

1、引入jar包

<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-bom</artifactId>
<type>pom</type>
<version>7.69.0.Final</version>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement> <dependencies>
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-compiler</artifactId>
</dependency>
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-mvel</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.11</version>
</dependency>
</dependencies>

2、编写kmodule.xml配置文件

此配置文件需要放置在resources/META-INF目录下。

<kmodule xmlns="http://www.drools.org/xsd/kmodule">
<!--
kbase 可以存在多个
name: 指定kbase的名字,需要是唯一的
packages: 包名,可以理解为到src/main/resources目录下查找这个包名下的规则文件,多个包使用逗号分割
default: 当前kbase是否是默认的kbase
-->
<kbase name="shop-kabse" packages="com.huan.shop" default="false">
<!--
ksession 可以存在多个
name: 指定ksession 的名字,需要唯一
defalut: 当前ksession在这个kbase下是否是默认的
type: 指定当前ksession是否是有状态的 stateless表示是无状态的
-->
<ksession name="shop-ksession" default="false" type="stateless"/>
<ksession name="shop-ksession-stateful" default="false" type="stateful"/>
</kbase>
</kmodule>

此处我们需要关注一下 kbasepackage的值,这个值需要和规则文件中的package值一致,否则会找不到规则,具体看下方。

3、编写规则文件

1、规则文件的语法

包名,必须放置在第一行
package
// 引入Java中的类,需要些全限定名
import // 定义function ,可选
function // Optional // 定义 query ,可选
query // Optional declare // Optional global // Optional // rule 关键字 "rule name" 规则的名字
rule "rule name"
// Attributes 属性可选
when // 关键字
// Conditions 条件,可为空
then
// Actions // 匹配后执行的结果
end // 关键字

2、编写规则文件

规则文件的名字无所谓,比如: book-discount.drl

// 包名,必须防止到第一行,这个名字需要和 kbase中package属性的值一致
package com.huan.shop /**
* 倒入类
*/
import com.huan.drools.CustomerOrder // 定义规则
rule "shop-rule-01"
when
// 模式匹配:到工作内存中查找CustomerOrder,并且这个对象的purchaseQuantity值需要是1,
// 如果条件成立,$order是绑定变量名,一般以$开头,和fact对象区分开
$order:CustomerOrder(purchaseQuantity == 1)
then
System.out.println("匹配规则 shop-rule-01");
// 赋值,此处赋值后,在Java代码中获取获取到赋值后的值
$order.setDiscount(1D);
end rule "shop-rule-02"
when
$order:CustomerOrder(purchaseQuantity == 2)
then
System.out.println("匹配规则 shop-rule-02");
$order.setDiscount(0.98);
end rule "shop-rule-03"
when
$order:CustomerOrder(purchaseQuantity >= 3)
then
System.out.println("匹配规则 shop-rule-03");
$order.setDiscount(0.85);
end

3、解释一下包名

如果 shop-discount.drl的包名修改为com.huan.shop1则会提示如下警告:

12:43:01.589 [main] WARN org.drools.compiler.kie.builder.impl.KieBuilderImpl - File 'com/huan/shop/shop-discount.drl' is in folder 'com/huan/shop' but declares package 'com.huan.shop1'. It is advised to have a correspondance between package and folder names.

四、编写Java代码

1、编写一个订单对象

此对象保存的是用户购买了几件衣服和对应的折扣。

/**
* 客户购买衣服的订单,省略 getter 和 setter 方法
*
* @author huan.fu
* @date 2022/5/12 - 11:27
*/
public class CustomerOrder {
/**
* 购买了几件衣服
*/
private Integer purchaseQuantity;
/**
* 最终打多少折
*/
private Double discount; public CustomerOrder(Integer purchaseQuantity) {
this.purchaseQuantity = purchaseQuantity;
}
}

2、编写测试代码

1、无状态测试方法statelessSessionTest规则规则2,即最终打0.98折。

2、有状态测试方法statefulSessionTest规则规则3,即最终打0.85折。

package com.huan.drools;

import org.kie.api.KieServices;
import org.kie.api.event.rule.DebugRuleRuntimeEventListener;
import org.kie.api.runtime.KieContainer;
import org.kie.api.runtime.KieSession;
import org.kie.api.runtime.StatelessKieSession; /**
* drools 测试类
*/
public class DroolsApplication {
public static void main(String[] args) throws InterruptedException {
// 无状态session测试
statelessSessionTest();
// 有状态session测试
statefulSessionTest();
} private static void statelessSessionTest() {
// 获取kie services
KieServices kieServices = KieServices.get();
// 获取kie容器对象
KieContainer kieContainer = kieServices.getKieClasspathContainer();
// 获取kie session , 此处获取的是无状态的session,因为 <ksession name="shop-ksession" default="false" type="stateless"/>
// 中type="stateless"就是无状态的session
StatelessKieSession kieSession = kieContainer.newStatelessKieSession("shop-ksession");
// 创建一个对象,可以理解为 Fact对象,即事实对象
CustomerOrder customerOrder = new CustomerOrder(2);
// 添加监听器,便于观察日志
kieSession.addEventListener(new DebugRuleRuntimeEventListener());
// 无状态的session只需要执行 execute 方法即可。
kieSession.execute(customerOrder); System.err.println("通过规则后,获取到的折扣为:" + customerOrder.getDiscount());
} private static void statefulSessionTest() {
// 获取kie services
KieServices kieServices = KieServices.get();
// 获取kie容器对象
KieContainer kieContainer = kieServices.getKieClasspathContainer();
// 获取kie session , 此处获取的是有状态的session
KieSession kieSession = kieContainer.newKieSession("shop-ksession-stateful");
// 创建一个对象,可以理解为 Fact对象,即事实对象
CustomerOrder customerOrder = new CustomerOrder(3);
// 添加监听器,便于观察日志
kieSession.addEventListener(new DebugRuleRuntimeEventListener());
// 将customerOrder对象加入到工作内存中
kieSession.insert(customerOrder);
// 触发所有的规则,如果只想触发指定的规则,则使用fireAllRules(AgendaFilter agendaFilter)方法
kieSession.fireAllRules(); // 有状态的session一定需要调用dispose方法
kieSession.dispose(); System.err.println("通过规则后,获取到的折扣为:" + customerOrder.getDiscount());
}
}

此处需要注意有状态session无状态session写法的区别。

五、测试结果

到此,我们使用drools实现的一个简单的案例就实现了。

六、drools引擎的基本组件

1、Rules:我们自己定义的业务规则,比如我们自己写的规则文件。所有规则必须至少包含触发规则的条件和规则规定的操作。

2、Production memory:规则存储在 Drools 引擎中的位置。

3、Facts:输入或更改到 Drools 引擎中的数据,Drools 引擎匹配规则条件以执行适用规则。在规则中修改了Fact对象的值,真实的JavaBean的数据也会发生改变。

比如:当我们调用ksession.insert(对象),那么插入的这个对象就可以理解成Facts对象。

4、Working memory:facts 在 Drools 引擎中存储的位置。

5、Pattern matcher:匹配器,将Rule Base中所有的规则与Working memory中的Fact对象进行模式匹配,匹配成功的规则将被激活并放入到Agenda中。

6、Agenda:议程,执行Agenda中被激活的排好序的规则。

七、完整代码

https://gitee.com/huan1993/spring-cloud-parent/tree/master/drools/drools-quickstart

八、参考文档

1、https://docs.drools.org/7.69.0.Final/drools-docs/html_single/index.html#decision-engine-con_decision-engine

drools的简单入门案例的更多相关文章

  1. MyBatis学习总结(一)简单入门案例

    MyBatis学习总结(一)简单入门案例 主要内容:本文主要通过对数据库中的use表进行增删改查总结mybatis的环境搭建和基本入门使用 一.需要的jar包: 1.核心包 2.依赖包 3.jdbc数 ...

  2. springcloud+eureka简单入门案例

    springcloud+eureka简单入门案例 一.服务提供者 直接提供服务,入门案例没有特别要设置的地方,注意下端口,由于要启动多个服务,可能会冲突 配置文件(src/main/resources ...

  3. Lucene介绍及简单入门案例(集成ik分词器)

    介绍 Lucene是apache软件基金会4 jakarta项目组的一个子项目,是一个开放源代码的全文检索引擎工具包,但它不是一个完整的全文检索引擎,而是一个全文检索引擎的架构,提供了完整的查询引擎和 ...

  4. Dubbo+Zookeeper的简单入门案例

    1.1  Dubbo简介 Apache Dubbo是一款高性能的Java RPC框架.其前身是阿里巴巴公司开源的一个高性能.轻量级的开源Java RPC框架,可以和Spring框架无缝集成. 什么是R ...

  5. javaweb 基于java Servlet登入 简单入门案例

    项目流程 第一步:创建一个java webproject第二步:创建三个界面,1,login.jsp 2 success.jsp 3 fail.jsp第三步:更改新建界面编码格式,utf-8 默然编码 ...

  6. Spring Boot 简单入门案例

    第一:打开idea 找到spring  Initializr 第二:点击Next 在点击下一步 找到web之后勾选第一个spring web 就完成了 在写一个类 点击运行 结果如下:

  7. EF CodeFirst系列(1)---CodeFirst简单入门

    1.什么是CodeFirst 从EF4.1开始,EF可以支持CodeFirst开发模式,这种开发模式特别适用于领域驱动设计(Domain Driven Design,大名鼎鼎的DDD).在CodeFi ...

  8. 1.Spring框架入门案例

    一.简单入门案例 入门案例:IoC 1.项目创建与结构 2.接口与实现类 User.java接口 package com.jd.ioc; /** * @author weihu * @date 201 ...

  9. Mybatis入门案例中设计模式的简单分析

    Talk is cheap, show me the code! public class TestMybatis { public static void main(String[] args) t ...

随机推荐

  1. 【Visual Studio】VS 提示图标的含义

    一.前言 vs 中提示图标是什么意思 二.正文 信号图标 以下信号图标应用于所有原有的图标并指示它们的辅助功能. 图标 描述 <No Signal Icon> Public. 可从此组件中 ...

  2. 我的python学习记_03

    数据类型 python中的数据类型包括:1.数字类型number:整型int(即整数) 浮点型float(小数形式,整数的话后面加".0") 布尔型(判断正确与否) 复数型(com ...

  3. Value注解获取值一直为Null

    @Value("${jwt.tokenHeader}") private String tokenHeader; 常见的错误解决办法如下: 1.使用static或final修饰了t ...

  4. django开发前准备工作

    安装pip(python包管理器,类似npm) 安装virtualenv(python虚拟环境,可以形成一个版本隔绝的文件夹) virtualenv使用方法 1,virtualenv  project ...

  5. 最长非降子序列的N*logN解法

    之前讲到过求最长非降子序列的O(N^2)解法. 链接 这次在原来的基础上介绍一下N*logN解法. 该解法主要是维护一个数组minValue,minValue[i]表示最长上身子序列长度为i的数的最小 ...

  6. python---双链表的常用操作

    class Node(object): """结点""" def __init__(self, data): self.data = dat ...

  7. Spring-AOP动态代理技术(底层代码)

    1.JDK代理:基于接口的动态代理技术 目标对象必须有接口,目标对象有什么方法,目标接口就有什么方法, 运行期间基于接口动态生成代理对象,所以代理对象也就有目标对象同样的方法. 注意:以下代码只是底层 ...

  8. Codeforeces 13B

    计算几何二维基础

  9. vue3组合式API

    vue3组合式API 为什么要用组合式API,我们来看看它是如何解决vue2的局限性的 1.vue2的局限性 当组件内容越来越多,逻辑越来越复杂,可读性就会降低,并且难以维护. vue2组件采用配置式 ...

  10. Divide by Zero 2021 and Codeforces Round #714 (Div. 2) B. AND Sequences思维,位运算 难度1400

    题目链接: Problem - B - Codeforces 题目 Example input 4 3 1 1 1 5 1 2 3 4 5 5 0 2 0 3 0 4 1 3 5 1 output 6 ...