Drools 快速指南

Drools 快速指南

Drools使用教程

注意:SpringBoot版本和Drools版本直接的兼容问题。

举例:某电商平台促销活动

活动规则是根据⽤户购买订单的⾦额给⽤户送相应的积分,购买的越多送的积分越多,⽤户可以使⽤积分来兑换相应的商品,我们这次活动的⼒度很⼤,肯定会吸引很多的⽤户参加,产品经理⼩王兴⾼采烈唾液横⻜的对⼩明讲到。

⽤户购买的⾦额和对应送多少积分的规则如下:

规则编号 名称 描述
1 充值100元以内 不加分
2 充值100元-500元 100分
3 充值500元-1000元 500分
4 充值1000元以上 1000分

⼩明⼀看,这需求果然简单呀,作为⼀个⼯作了两三年的程序员来讲,这不就是⼩case,半天搞

定,送积分的⼼代码如下:

   public void execute() throws Exception {
        List<Order> orderList = getInitData();
        for (int i = 0; i < orderList.size(); i++) {
            Order order = orderList.get(i);
            if (order.getAmout() <= 100) {
                order.setScore(0);
                addScore(order);
            } else if (order.getAmout() > 100 && order.getAmout() <= 500) {
                order.setScore(100);
                addScore(order);
            } else if (order.getAmout() > 500 && order.getAmout() <= 1000) {
                order.setScore(500);
                addScore(order);
            } else {
                order.setScore(1000);
                addScore(order);
            }
        }
    }

正当⼩明得意洋洋的打了个最新版本投产上线之后,产品经理⼩王说积分规则层次太少了,由以前的4组变成8组,⼩明此刻的⼼情:kao …

⼩明想这样下去⾮得被他们弄死,必须要找找有什么技术可以将活动规则和代码解耦,不管规则如何变化,执⾏端不⽤动。⼩明搜了半天还真有这样的东⻄,那就是规则引擎,那么规则引擎到

底是什么东⻄呢?我们来看看。

1. 规则引擎

规则引擎:全称为业务规则管理系统,英⽂名为BRMS(即Business Rule Management

System)。规则引擎的主要思想是将应⽤程序中的业务决策部分分离出来,并使⽤预定义的语义模块编写业务决策(业务规则),由⽤户或开发者在需要时进⾏配置、管理。 需要注意的是规则引擎并不是⼀个具体的技术框架,⽽是指的⼀类系统,即业务规则管理系统。 ⽬前市⾯上具体的规则引擎产品有:drools、VisualRules、iLog等,使⽤最为⼴泛并且开源的是Drools。 规则引擎实现了将业务决策从应⽤程序代码中分离出来,接收数据输⼊,解释业务规则,并根据业务规则做出业务决策。规则引擎其实就是⼀个输⼊输出平台。

规则引擎主要应⽤场景

对于⼀些存在⽐较复杂的业务规则并且业务规则会频繁变动的系统⽐较适合使⽤规则引擎,如下:

1、⻛险控制系统----⻛险贷款、⻛险评估

2、反欺诈项⽬----银⾏贷款、征信验证

3、决策平台系统----财务计算

4、促销平台系统----满减、打折、加价购

2. Drools介绍

drools是⼀款由JBoss组织提供的基于Java语⾔开发的开源规则引擎,可以将复杂且多变的业务规则从硬编码中解放出来,以规则脚本的形式存放在⽂件或特定的存储介质中(例如存放在数据库中),使得业务规则的变更不需要修改项⽬代码、不⽤重启服务器就可以在线上环境⽴即⽣效。

drools官⽹地址 :https://drools.org/

drools源码下载地址 :https://github.com/kiegroup/drools

● 使⽤规则引擎能够解决什么问题? 针对复杂的业务规则代码处理,往往存在⼀下问题:

1、硬编码实现业务规则难以维护;

2、硬编码实现业务规则难以应对变化;

3、业务规则发⽣变化需要修改代码,重启服务后才能⽣效; 于是规则引擎Drools便诞⽣在项⽬

中。。。

● 使⽤规则引擎的优势如下:

1、业务规则与系统代码分离,实现业务规则的集中管理

2、在不重启服务的情况下可随时对业务规则进⾏扩展和维护

3、可以动态修改业务规则,从⽽快速响应需求变更

4、规则引擎是相对独⽴的,只关⼼业务规则,使得业务分析⼈员也可以参与编辑、维护系统的业

务规则

5、减少了硬编码业务规则的成本和⻛险

6、使⽤规则引擎提供的规则编辑⼯具,使复杂的业务规则实现变得的简单

drools API开发步骤如下:

  1. 获取KieServices

  2. 获取KieContainer

  3. KieSession

  4. Insert fact

  5. 触发规则

  6. 关闭KieSession

3. Demo

3.1 业务场景说明

规则编号 名称 描述
1 充值100元以内 不加分
2 充值100元-500元 100分
3 充值500元-1000元 500分
4 充值1000元以上 1000分

3.2 导包

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.drools</groupId>
            <artifactId>drools-compiler</artifactId>
            <version>7.73.0.Final</version>
        </dependency>
        <dependency>
            <groupId>org.drools</groupId>
            <artifactId>drools-mvel</artifactId>
            <version>7.73.0.Final</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

3.3 kmodule.xml配置⽂件

根据drools要求创建resources/META-INF/kmodule.xml配置⽂件

需要有⼀个配置⽂件告诉代码规则⽂件drl在哪⾥,在drools中这个⽂件就是kmodule.xml,放置到resources/META-INF⽬录下。

<kmodule xmlns="http://www.drools.org/xsd/kmodule">
    <!--
    name:指定kbase的名称,可以任意,但是需要唯一
    packages:指定规则文件的目录,需要根据实际情况填写,否则无法加载到规则文件
    default:指定当前kbase是否为默认
    -->
    <kbase name="myKbase1" packages="drools.rules" default="true">
        <!--
        name:指定ksession名称,可以任意,但是需要唯一
        default:指定当前session是否为默认
        -->
        <ksession name="ksession-rule" default="true"/>
    </kbase>
</kmodule>

注意:上⾯配置⽂件的名称和位置都是固定写法,不能更改

Kmodule 中可以包含⼀个到多个 kbase,分别对应 drl 的规则⽂件。Kbase 需要⼀个唯⼀的 name,可以取任意字符串。packages 为drl⽂件所在resource⽬录下的路径。注意区分drl⽂件中的package与此处的package不⼀定相同。多个包⽤逗号分隔。默认情况下会扫描 resources⽬录下 所有(包含⼦⽬录)规则⽂件。kbase的default属性,标示当前KieBase是不是默认的,如果是默认的则不⽤名称就可以查找到该KieBase,但每个 module 最多只能有⼀个默认 KieBase。kbase 下⾯可以有⼀个或多个 ksession, ksession 的 name 属性必须设置,且必须唯⼀。

3.4 创建实体类Order

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Order {
    private int amout;
    private int score;
}

3.5 创建规则⽂件resources/drools/rules/score-rules.drl

package drools.rules

import com.example.drools.entity.Order

// 规则1 100元以下不加分
rule "score_1"
when
$s : Order(amout <= 100)
then
 $s.setScore(0);
 System.out.println("成功匹配到规则1: 100元以下,不加分");
end

// 规则2 100元-500元 加100分
rule "score_2"
when
$s : Order(amout > 100 && amout <= 500)
then
 $s.setScore(100);
 System.out.println("成功匹配到规则2:100元-500元 加100分");
end

// 规则3 500元-1000元 加500分
rule "score_3"
when
$s : Order(amout > 500 && amout <= 1000)
then
 $s.setScore(500);
 System.out.println("成功匹配到规则3:500元-1000元 加500分");
end

// 规则3 500元-1000元 加500分
rule "score_4"
when
$s : Order(amout > 1000)
then
 $s.setScore(1000);
 System.out.println("成功匹配到规则4:1000元 以上 加1000分");
end

3.6 编写单元测试

    /**
     * @description 积分测试
     * @author yz
     * @date 2022/08/29
     **/
    @Test
    public void integralTest() {
        KieServices kieServices = KieServices.Factory.get();
        KieContainer kieContainer = kieServices.getKieClasspathContainer();
        //会话对象,用于和规则引擎对象
        KieSession kieSession = kieContainer.newKieSession();
        //构造订单对象,设置订单金额,由规则引擎计算获得积分
        Order order = new Order(200, 0);
        //将数据交给规则引擎,由规则引擎计算获取积分
        kieSession.insert(order);
        //激活规则引擎,如果匹配成功则执⾏规则
        kieSession.fireAllRules();
        //关闭会话
        kieSession.dispose();
        //打印结果;
        System.out.println("订单提交之后积分:" + order.getScore());
    }

执行结果:

2022-08-29 18:36:40.691  INFO 26040 --- [           main] o.d.c.kie.builder.impl.KieContainerImpl  : Start creation of KieBase: defaultKieBase
2022-08-29 18:36:40.694  INFO 26040 --- [           main] o.d.c.kie.builder.impl.KieContainerImpl  : End creation of KieBase: defaultKieBase
成功匹配到规则2:100元-500元 加100分
订单提交之后积分:100

4. Drools基础语法

drl是Drools Rule Language的缩写。在规则⽂件中编写具体的规则内容。

⼀套完整的规则⽂件内容构成如下:

  • package:包名,package对应的不⼀定是真正的⽬录,可以任意写com.abc,同⼀个包下的drl⽂

件可以相互访问

  • import:⽤于导⼊类或者静态⽅法

  • global:全局变量

  • function:⾃定义函数

  • query:查询

  • rule end:规则体

规则体语法结构

⼀个规则通常包括三个部分:属性部分(attribute) 、条件部分(LHS)和结果部分(RHS)

    rule "ruleName" //rule关键字,表示规则开始,参span数为规则的唯⼀名称
    attributes //规则属性,是rule与when之间的参数,为可选项
    when //关键字,后⾯是规则的条件部分
        LHS //Left Hand Side,是规则的条件部分
    then //后⾯跟规则的结果部分 
        RHS //是规则的结果或⾏为
    end //表示⼀个规则的结束

LHS

(Left Hand Side):是规则的条件部分的通⽤名称。它由零个或多个条件元素组成。如果LHS为空,则它将被视为始终为true的条件元素。 (左⼿边)

LHS部分由⼀个或者多个条件组成,条件⼜称为pattern.

其中绑定变量名可以省略,通常绑定变量名的命名⼀般建议以$开始。如果定义了绑定变量名,就可以

在规则体的RHS部分使⽤此绑定变量名来操作相应的Fact对象。Field约束部分是需要返回true或者false

的0个或多个表达式。

     //规则1:100元以下, 不加分
     rule "score_1"
     when
     // ⼯作内存中必须存在Order这种类型的Fact对象-----类型约束 
     // Fact对象的amout属性值必须⼩于等于100------属性约束 
     $s : Order(amout <= 100) 
     then
     $s.setScore(0); 
     System.out.println("成功匹配到规则1:100元以下, 不加分 "); 
     end

如果 LHS 部分为空的话,那么引擎会⾃动添加⼀个 eval(true)的条件,由于该条件总是返回 true,所以 LHS 为空的规则总是返回 true

约束连接

在 LHS 当中,可以包含 0~n 个条件,多个pattern之间可以采⽤“&&” (and) 、 “||”(or)和“,”(and)来实现,也可以不写,默认连接为and。

//规则2:100元-500元 加100分
rule "score_2"
when
$s : Order(amout > 100 && amout <= 500)
then
$s.setScore(100);
System.out.println("成功匹配到规则2:100元-500元 加100分 ");
end

⽐较操作符

在 Drools当中共提供了⼗⼆种类型的⽐较操作符, 分别是: >、 >=、 <、 <=、 = =、 !=、

contains、 not contains、memberof、not memberof、matches、not matches;在这⼗⼆种类型的

⽐较操作符当中,前六个是⽐较常⻅也是⽤的⽐较多的⽐较操作符

符号 说明
> 大于
< 小于
>= 大于等于
<= 小于等于
== 等于
!= 不等于
contains 检查一个Fact对象的某个属性值是否包含一个指定的对象值
not contains 检查一个Fact对象的某个属性值是否不包含一个指定的对象值
memberOf 判断一个Fact对象的某个属性是否在一个或多个集合中
not memberOf 判断一个Fact对象的某个属性是否不在一个或多个集合中
matches 判断一个Fact对象的属性是否与提供的标准的Java正则表达式进行匹配
not matches 判断一个Fact对象的属性是否不与提供的标准的Java正则表达式进行匹配

示例代码


//测试contains规则
rule "rule"
when
$ordre:Order();
$customer:Customer(orderList contains $ordre);
then
System.out.println("测试contains规则触发:"+$customer.getName());
end

//测试not contains规则
rule "rule2"
when
$order:Order();
$customer:Customer(orderList not contains $order);
then
System.out.println("测试not contains规则触发:"+$customer.getName());
end

//测试⽐较操作符matches
rule "rule3"
when
Customer(name matches "张.*")
then
System.out.println("测试⽐较操作符matches触发...");
end

//测试⽐较操作符not matches
rule "rule4"
when
Customer(name not matches "张.*")
then
System.out.println("测试⽐较操作符not matches触发...");
end

5.结果部分

5.1 insert

函数insert的作用与我们在Java类当中调用StatefulKnowledgeSession对象的insert方法的作用相同,都是用来将一个 Fact 对象插入到当前的 Working Memory 当中

需注意:一旦调用insert 宏函数,那么 Drools 会重新与所有的规则再重新匹配一次, 对于没有设置no-loop 属性为 true 的规则,如果条件满足,不管其之前是否执行过都会再执行一次,这个特性不仅存在于 insert 宏函数上,后面介绍的 update、retract 宏函数同样具有该特性,所以在某些情况下因考虑不周调用insert、update 或 retract 容易发生死循环

示例:


//Drools提供的内置方法insert
rule "rule5"
when
eval(true); //默认成立
then
Customer cus=new Customer();
cus.setName("张三");
insert(cus);
System.out.println("测试Drools提供的内置方法insert 触发...");
end
  
rule "rule6"
when
$customer:Customer(name =="张三");
then
System.out.println("测试Drools提供的内置方法insert 触发..."+$customer.getName());
end

测试:

    @Test
    public void insert() {
        KieServices kieServices = KieServices.Factory.get();
        KieContainer kieContainer = kieServices.getKieClasspathContainer();
        //会话对象,用于和规则引擎交互
        KieSession kieSession = kieContainer.newKieSession();
        //激活规则引擎,如果匹配成功则执行规则
        kieSession.fireAllRules();
        //关闭会话
        kieSession.dispose();
    }

insertLogical

insertLogical 作用与 insert 类似,它的作用也是将一个 Fact 对象插入到当前的 Working Memroy 当中

5.2 update

update函数意义与其名称一样, 用来实现对当前Working Memory当中的 Fact进行更新,用来告诉当前的 Working Memory 该 Fact 对象已经发生了变化。

rule "rule7"
//no-loop true
when
$customer:Customer(name =="李四");
then
$customer.setName("张三");
update($customer);
System.out.println("测试Drools提供的内置⽅法update触发...");
end

rule "rule8"
when
$customer:Customer(name =="张三");
then
System.out.println("测试Drools提供的内置⽅法update触发..."+$customer.getName());
end

测试

    @Test
    public void update() {
        KieHelper kieHelper = new KieHelper();
        // 获取指定的drl文件
        Resource resource = ResourceFactory.newClassPathResource("drools/rules/customer-rules.drl", "UTF-8");
        kieHelper.addResource(resource, ResourceType.DRL);
        KieBase kieBase = kieHelper.build();
        //会话对象,用于和规则引擎对象
        KieSession kieSession = kieBase.newKieSession();
        Customer customer = new Customer();
        customer.setName("李四");
        kieSession.insert(customer);
        //激活规则引擎,如果匹配成功则执行规则
        kieSession.fireAllRules();
        //关闭会话
        kieSession.dispose();
    }

5.3 retract

retract用来将 Working Memory 当中某个 Fact 对象从 Working Memory 当中删除


//Drools提供的内置方法retract
rule "rule9"
when
$customer:Customer(name =="李四");
then
retract($customer);
System.out.println("测试Drools提供的内置方法retract 触发...");
end
rule "rule10"
when
$customer:Customer();
then
System.out.println("测试Drools提供的内置方法retract 触发..."+$customer.getName());
end

测试:

@Test
public void retract() {
    KieHelper kieHelper = new KieHelper();
    // 获取指定的drl文件
    Resource resource = ResourceFactory.newClassPathResource("drools/rules/customer-rules.drl", "UTF-8");
    kieHelper.addResource(resource, ResourceType.DRL);
    KieBase kieBase = kieHelper.build();
    //会话对象,用于和规则引擎对象
    KieSession kieSession = kieBase.newKieSession();
    Customer customer = new Customer();
    customer.setName("李四");
    kieSession.insert(customer);
    //激活规则引擎,如果匹配成功则执行规则
    kieSession.fireAllRules();
    //关闭会话
    kieSession.dispose();
}

6.属性部分

属性名 说明
salience 指定规则执行优先级
dialect 指定规则使用的语言类型,取值为java和mvel
enabled 指定规则是否启用
date-effective 指定规则生效时间
date-expires 指定规则失效时间
activation-group 激活分组,具有相同分组名称的规则只能有一个规则触发
agenda-group 议程分组,只有获取焦点的组中的规则才有可能触发
timer 定时器,指定规则触发的时间
auto-focus 自动获取焦点,一般结合agenda-group一起使用
no-loop 防止死循环

6.1 salience

作用是用来设置规则执行的优先级,salience 属性的值是一个数字,数字越大执行优先级越高。默认情况下,规则的 salience 默认值为 0。如果不设置salience属性,规则体的执行顺序为由上到下。

示例:

rule "attributes_rule1"
salience 1
when
eval(true)
then
System.out.println("rule1....");
end
rule "attributes_rule2"
salience 2
when
eval(true)
then
System.out.println("rule2....");
end

测试:

@Test
public void salience() {
    KieHelper kieHelper = new KieHelper();
    // 获取指定的drl文件
    Resource resource = ResourceFactory.newClassPathResource("drools/rules/attribute-rule.drl", "UTF-8");
    kieHelper.addResource(resource, ResourceType.DRL);
    KieBase kieBase = kieHelper.build();
    KieSession kieSession = kieBase.newKieSession();
    //激活规则引擎,如果匹配成功则执行规则
    kieSession.fireAllRules();
    //关闭会话
    kieSession.dispose();
}

运行结果:

rule2....
rule1....

6.2 no-loop

作用是用来控制已经执行过的规则在条件再次满足时是否再次执行。no-loop 属性的值是一个布尔型,默认情况下规则的 no-loop属性的值为 false,如果 no-loop 属性值为 true,那么就表示该规则只会被引擎检查一次,

如果满足条件就执行规则的 RHS 部分,如果引擎内部因为对 Fact 更新引起引擎再次启动检查规则,那么它会忽略掉所有的 no-loop 属性设置为 true 的规则。

//测试no-loop
rule "attributes_rule3"
salience 1
no-loop true
when
$customer:Customer(name=="张三")
then
update($customer);
System.out.println("customer name:"+$customer.getName());
end

测试:

@Test
public void noLoop() {
    KieHelper kieHelper = new KieHelper();
    // 获取指定的drl文件
    Resource resource = ResourceFactory.newClassPathResource("drools/rules/attribute-rule.drl", "UTF-8");
    kieHelper.addResource(resource, ResourceType.DRL);
    KieBase kieBase = kieHelper.build();
    KieSession kieSession = kieBase.newKieSession();
    Customer customer = new Customer();
    customer.setName("张三");
    kieSession.insert(customer);
    //激活规则引擎,如果匹配成功则执行规则
    kieSession.fireAllRules(new RuleNameEqualsAgendaFilter("attributes_rule3"));
    //关闭会话
    kieSession.dispose();
}

结果:

customer name:张三

6.3 date-effective

作用是用来控制规则只有在到达后才会触发,在规则运行时,引擎会自动拿当前操作系统的时候与 date-effective 设置的时间值进行比对, 只有当系统时间>=date-effective 设置的时间值时,规则才会触发执行,否则执行将不执行。在没有设置该属性的情况下,规则随时可以触发,没有这种限制。

date-effective 的值为一个日期型的字符串,默认情况下,date-effective 可接受的日期格式为“dd-MMM-yyyy”

在实际使用的过程当中,如果您不想用这种时间的格式,那么可以在调用的 Java 代码中通过使用System.setProperty(String key,String value)方法来修改默认的时间格式

在java文件中添加此条命令: System.setProperty("drools.dateformat","yyyy-MM-dd");

//测试date-effective 系统时间>date-expires=true
rule "attributes_rule4"
date-effective "2021-11-20"
//当前日期不小于2021-11-25时可以执行
when
eval(true);
then
System.out.println("attributes_rule4 is execution!");
end

测试:

@Test
public void dateEffective() {
    System.setProperty("drools.dateformat", "yyyy-MM-dd");
    KieHelper kieHelper = new KieHelper();
    // 获取指定的drl文件
    Resource resource = ResourceFactory.newClassPathResource("drools/rules/attribute-rule.drl", "UTF-8");
    kieHelper.addResource(resource, ResourceType.DRL);
    KieBase kieBase = kieHelper.build();
    KieSession kieSession = kieBase.newKieSession();
    //激活规则引擎,如果匹配成功则执行规则
    kieSession.fireAllRules(new RuleNameEqualsAgendaFilter("attributes_rule4"));
    //关闭会话
    kieSession.dispose();
}

6.4 date-expires

//测试date-expires 系统时间<date-expires=true
rule "attributes_rule5"
date-expires "2022-11-20"
//当前日期不小于2021-11-25时可以执行
when
eval(true);
then
System.out.println("attributes_rule5 is execution!");
end

测试:

@Test
public void dateExpires() {
    System.setProperty("drools.dateformat", "yyyy-MM-dd");
    KieHelper kieHelper = new KieHelper();
    // 获取指定的drl文件
    Resource resource = ResourceFactory.newClassPathResource("drools/rules/attribute-rule.drl", "UTF-8");
    kieHelper.addResource(resource, ResourceType.DRL);
    KieBase kieBase = kieHelper.build();
    KieSession kieSession = kieBase.newKieSession();
    //激活规则引擎,如果匹配成功则执行规则
    kieSession.fireAllRules(new RuleNameEqualsAgendaFilter("attributes_rule5"));
    //关闭会话
    kieSession.dispose();
}

6.5 enabled

作用是用来定义一个规则是否可用的。该属性的值是一个布尔值,默认该属性的值为 true,表示规则是可用的。设置其 enabled 属性值为 false,那么引擎就不会执行该规则

//测试enabled
rule "attributes_rule6"
enabled false
when
eval(true);
then
System.out.println("attributes_rule6 is execution!");
end

测试:

 @Test
    public void enabled() {
        System.setProperty("drools.dateformat", "yyyy-MM-dd");
        KieHelper kieHelper = new KieHelper();
        // 获取指定的drl文件
        Resource resource = ResourceFactory.newClassPathResource("drools/rules/attribute-rule.drl", "UTF-8");
        kieHelper.addResource(resource, ResourceType.DRL);
        KieBase kieBase = kieHelper.build();
        KieSession kieSession = kieBase.newKieSession();
        // 只匹配规则名称是已 activation_group_ 开头的规则,忽略其余的规则
        kieSession.fireAllRules(new RuleNameEqualsAgendaFilter("attributes_rule6"));
        //关闭会话
        kieSession.dispose();
    }

6.6 dialect

作用是用来定义规则当中要使用的语言类型,目前支持两种类型的语言:mvel 和 java,默认情况下,如果没有手工设置规则的 dialect,那么使用的 java 语言

6.7 activation-group

作用是将若干个规则划分成一个组,用一个字符串来给这个组命名,这样在执行的时候,具有相同 activation-group 属性的规则中只要有一个会被执行,其它的规则都将不再执行。

也就是说,在一组具有相同 activation-group 属性的规则当中,只有一个规则会被执行,其它规则都将不会被执行。当然对于具有相同 activation-group 属性的规则当中究竟哪一个会先执行,则可以用类似 salience 之类属性来实现。

//测试activation-group
rule "activation_group_7"
activation-group "test"
when
eval(true)
then
System.out.println("activation_group_7 execute...");
end

rule "activation_group_8"
activation-group "test"
when
eval(true)
then
System.out.println("activation_group_8 execute...");
end

测试:

    @Test
    public void activationGroup() {
        System.setProperty("drools.dateformat", "yyyy-MM-dd");
        KieHelper kieHelper = new KieHelper();
        // 获取指定的drl文件
        Resource resource = ResourceFactory.newClassPathResource("drools/rules/attribute-rule.drl", "UTF-8");
        kieHelper.addResource(resource, ResourceType.DRL);
        KieBase kieBase = kieHelper.build();
        KieSession kieSession = kieBase.newKieSession();
        // 只匹配规则名称是已 activation_group_ 开头的规则,忽略其余的规则
        kieSession.fireAllRules(new RuleNameStartsWithAgendaFilter("activation_group_"));
        //关闭会话
        kieSession.dispose();
    }

6.8 agenda-group

作用是agenda-group 属性的值也是一个字符串,通过这个字符串,可以将规则分为若干个Agenda Group,默认情况下,引擎在调用这些设置了 agenda-group 属性的规则的时候需要显示的指定某个 Agenda Group 得到 Focus (焦点) , 这样位于该 Agenda Group 当中的规则才会触发执行,否则将不执行。

//测试agenda-group
rule "agenda_group_9"
agenda-group "001"
when
eval(true)
then
System.out.println("agenda-group_9 execute...");
end

rule "agenda_group_10"
agenda-group "002"
when
eval(true)
then
System.out.println("agenda-group_10 execute...");
end

测试:

@Test
public void agendaGroup() {
    System.setProperty("drools.dateformat", "yyyy-MM-dd");
    KieHelper kieHelper = new KieHelper();
    // 获取指定的drl文件
    Resource resource = ResourceFactory.newClassPathResource("drools/rules/attribute-rule.drl", "UTF-8");
    kieHelper.addResource(resource, ResourceType.DRL);
    KieBase kieBase = kieHelper.build();
    KieSession kieSession = kieBase.newKieSession();
    kieSession.getAgenda().getAgendaGroup("002").setFocus();
    // 只匹配规则名称是已 activation_group_ 开头的规则,忽略其余的规则
    kieSession.fireAllRules(new RuleNameStartsWithAgendaFilter("agenda_group_"));
    //关闭会话
    kieSession.dispose();
}

6.9 auto-focus

作用是用来在已设置了 agenda-group 的规则上设置该规则是否可以自动独取 Focus,如果该属性设置为 true,那么在引擎执行时,就不需要显示的为某个 Agenda Group 设置 Focus,否则需要。


rule "agenda_group_11"
agenda-group "003"
auto-focus true
when
eval(true)
then
System.out.println("auto-focus_11 execute...");
end

测试:

@Test
public void autoFocus() {
    System.setProperty("drools.dateformat", "yyyy-MM-dd");
    KieHelper kieHelper = new KieHelper();
    // 获取指定的drl文件
    Resource resource = ResourceFactory.newClassPathResource("drools/rules/attribute-rule.drl", "UTF-8");
    kieHelper.addResource(resource, ResourceType.DRL);
    KieBase kieBase = kieHelper.build();
    KieSession kieSession = kieBase.newKieSession();
    // 只匹配规则名称是已 activation_group_ 开头的规则,忽略其余的规则
    kieSession.fireAllRules(new RuleNameStartsWithAgendaFilter("agenda_group_"));
    //关闭会话
    kieSession.dispose();
}

6.10 timer

timer属性可以通过定时器的方式指定规则执行的时间,使用方式有两种:

方式一:timer (int: ?)

此种方式遵循java.util.Timer对象的使用方式,第一个参数表示几秒后执行,第二个参数表示每隔几秒执行一次,第二个参数为可选。

方式二:timer(cron: )

此种方式使用标准的unix cron表达式的使用方式来定义规则执行的时间。

示例:

package drools.rules

import java.text.SimpleDateFormat
import java.util.Date

/*
此规则文件用于测试timer属性
*/
rule "rule_timer_1"
timer (5s 2s) //含义:5秒后触发,然后每隔2秒触发一次
when
then
System.out.println("规则rule_timer_1触发,触发时间为:" +
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
end
rule "rule_timer_2"
timer (cron:0/1 * * * * ?) //含义:每隔1秒触发一次
when
then
System.out.println("规则rule_timer_2触发,触发时间为:" +
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
end

测试:

@Test
public void timer() throws InterruptedException {
    //设置修改默认的时间格式
    System.setProperty("drools.dateformat", "yyyy-MM-dd");
    KieHelper kieHelper = new KieHelper();
    // 获取指定的drl文件
    Resource resource = ResourceFactory.newClassPathResource("drools/rules/attribute-rule-timer.drl", "UTF-8");
    kieHelper.addResource(resource, ResourceType.DRL);
    KieBase kieBase = kieHelper.build();
    KieSession kieSession = kieBase.newKieSession();
    new Thread(() -> {
        //启动规则引擎进行规则匹配,直到调用halt方法才结束规则引擎
        kieSession.fireUntilHalt();
    }).start();
    Thread.sleep(10000);
    //结束规则引擎
    kieSession.halt();
    kieSession.dispose();
}

注意:单元测试的代码和以前的有所不同,因为我们规则文件中使用到了timer进行定时执行,需要程序能够持续一段时间才能够看到定时器触发的效果。

7. Drools高级语法

关键字 描述
package 包名,只限于逻辑上的管理,同一个包名下的查询或者函数可以直接调用
import 用于导入类或者静态方法
global 全局变量
function 自定义函数
query 查询
rule end 规则体

7.1 global全局变量

全局变量,一般用作执行规则后的结果数据返回或对具体某个服务调用等,如一个电子邮件服务的实例,

在调用规则引擎的集成代码中,获取emailService对象,然后将其设置在工作内存中。

语法结构为:global 对象类型 对象名称

示例:

package drools.rules

global java.util.List myGlobalList;//如果在规则文件中使用了全局变量,那么必须在规则引擎中设置全局变量值
rule "global_rule1"
when
eval( true )
then
myGlobalList.add( "Hello World" );//
System.out.println("global_rule1 is execution...");
end
rule "global_rule2"
when
eval( true )
then
System.out.println("myGlobalList集合的元素个数:"+myGlobalList.size());
System.out.println("global_rule2 is execution...");
end

测试:

@Test
public void global() {
    KieHelper kieHelper = new KieHelper();
    // 获取指定的drl文件
    Resource resource = ResourceFactory.newClassPathResource("drools/rules/globa-ruls.drl", "UTF-8");
    kieHelper.addResource(resource, ResourceType.DRL);
    KieBase kieBase = kieHelper.build();
    KieSession kieSession = kieBase.newKieSession();
    List list = new ArrayList();
    //设置全局变量,名称和类型必须和规则文件中定义的全局变量名称对应
    kieSession.setGlobal("myGlobalList", list);
    //激活规则引擎,如果匹配成功则执行规则
    kieSession.fireAllRules();
    kieSession.dispose();
    System.out.println("list.size():" + list.size());
}

7.2 query查询

query语法提供了一种查询working memory中符合约束条件的FACT对象的简单方法。它仅包含规则文件中的LHS部分,不用指定“when”和“then”部分。Query有一个可选参数集合,每一个参数都有可选的类型。如果没有指定类型,则默认为Object类型。引擎会尝试强转为需要的类型。对于KieBase来说,query的名字是全局性的,因此不要向同一RuleBase 的不同包添加相同名称的query。

使用ksession.getQueryResults(“name”)方法可以获得查询的结果,其中name为query的名称,方法的返回结果一个列表,从中可以获取匹配查询到的对象。

package drools.rules
import com.example.drools.entity.Person
//不带参数的查询
//当前query用于查询Working Memory中age>30的Person对象
query "query-1"
$person : Person(age > 30)
end
//带有参数的查询
//当前query用于查询Working Memory中age>30同时name需要和传递的参数name相同的Person对象
query "query-2"(String nameParam)
$person : Person(age > 30,name == nameParam)
end

测试:

@Test
public void query() throws InterruptedException {
    KieHelper kieHelper = new KieHelper();
    // 获取指定的drl文件
    Resource resource = ResourceFactory.newClassPathResource("drools/rules/query-ruls.drl", "UTF-8");
    kieHelper.addResource(resource, ResourceType.DRL);
    KieBase kieBase = kieHelper.build();
    KieSession kieSession = kieBase.newKieSession();
    Person p1 = new Person();
    p1.setName("JACK1");
    p1.setAge(29);
    Person p2 = new Person();
    p2.setAge(40);
    p2.setName("JACK2");
    kieSession.insert(p1);
    kieSession.insert(p2);
    //激活规则引擎,如果匹配成功则执行规则
    kieSession.fireAllRules();
    //通过规则过滤器实现只执行指定规则
    //kieSession.fireAllRules(new RuleNameEqualsAgendaFilter("rule5"));
    QueryResults results = kieSession.getQueryResults("query-1");
    System.out.println("results size is " + results.size());
    for (QueryResultsRow row : results) {
        Person person = (Person) row.get("$person");
        System.out.println("Person from WM, age : " + person.getAge());
    }
    System.out.println("-----------------------");
    QueryResults results2 = kieSession.getQueryResults("query-2", "JACK1");
    for (QueryResultsRow row : results2) {
        Person person = (Person) row.get("$person");
        System.out.println(person);
    }
    kieSession.dispose();
}

7.3 function函数

在规则中可以通过函数来做一些通用的逻辑,就相当于java类中的方法一样。函数的返回类型与参数类型与java的规则一样

package drools.rules
import com.example.drools.entity.Person

function String format(String name) {
return "hello " + name;
}
//测试函数
rule "function_rule"
when
$person:Person(name!=null&&age>30)
then
//调用上面定义的函数
String ret = format($person.getName());
System.out.println(ret);
end

测试:

@Test
public void function() {
    KieHelper kieHelper = new KieHelper();
    // 获取指定的drl文件
    Resource resource = ResourceFactory.newClassPathResource("drools/rules/function-ruls.drl", "UTF-8");
    kieHelper.addResource(resource, ResourceType.DRL);
    KieBase kieBase = kieHelper.build();
    KieSession kieSession = kieBase.newKieSession();
    Person p1 = new Person();
    p1.setName("JACK1");
    p1.setAge(29);
    Person p2 = new Person();
    p2.setAge(40);
    p2.setName("JACK2");
    kieSession.insert(p1);
    kieSession.insert(p2);
    //激活规则引擎,如果匹配成功则执行规则
    kieSession.fireAllRules();
    kieSession.dispose();
}

8.Spring Boot整合Drools

**第一步:创建maven工程并配置pom.xml **

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.3</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>Drools</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>Drools</name>
    <description>Drools</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.drools</groupId>
            <artifactId>drools-compiler</artifactId>
            <version>7.73.0.Final</version>
        </dependency>
        <dependency>
            <groupId>org.drools</groupId>
            <artifactId>drools-mvel</artifactId>
            <version>7.73.0.Final</version>
        </dependency>
        <dependency>
            <groupId>org.kie</groupId>
            <artifactId>kie-api</artifactId>
            <version>7.73.0.Final</version>
        </dependency>
        <dependency>
            <groupId>org.kie</groupId>
            <artifactId>kie-spring</artifactId>
            <version>7.73.0.Final</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

第二步:创建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="drools.rules" default="true">
        <!--
        name:指定ksession名称,可以任意,但是需要唯一
        default:指定当前session是否为默认
        -->
        <ksession name="ksession-rule" default="true"/>
    </kbase>
</kmodule>

第三步:创建/resources/application.yml文件

server:
  port: 8088
spring:
  application:
    name: spring-boot-drools

**第四步:创建规则文件/resources/drools/rules/score-rules.drl **

package drools.rules

import com.example.drools.entity.Order

// 规则1 100元以下不加分
rule "score_1"
when
$s : Order(amout <= 100)
then
 $s.setScore(0);
 System.out.println("成功匹配到规则1: 100元以下,不加分");
end

// 规则2 100元-500元 加100分
rule "score_2"
when
$s : Order(amout > 100 && amout <= 500)
then
 $s.setScore(100);
 System.out.println("成功匹配到规则2:100元-500元 加100分");
end

// 规则3 500元-1000元 加500分
rule "score_3"
when
$s : Order(amout > 500 && amout <= 1000)
then
 $s.setScore(500);
 System.out.println("成功匹配到规则3:500元-1000元 加500分");
end

// 规则3 500元-1000元 加500分
rule "score_4"
when
$s : Order(amout > 1000)
then
 $s.setScore(1000);
 System.out.println("成功匹配到规则4:1000元 以上 加1000分");
end

第五步:编写配置类DroolsConfig

package com.example.drools.config;

import org.kie.api.KieBase;
import org.kie.api.KieServices;
import org.kie.api.builder.KieBuilder;
import org.kie.api.builder.KieFileSystem;
import org.kie.api.builder.KieRepository;
import org.kie.api.runtime.KieContainer;
import org.kie.internal.io.ResourceFactory;
import org.kie.spring.KModuleBeanFactoryPostProcessor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.io.Resource;

import java.io.IOException;

/**
 * @author yz
 * @description drools配置
 * @date 2022/08/29
 **/


@Configuration
public class DroolsConfig {
    //指定规则文件存放的目录
    private static final String RULES_PATH = "drools/rules/";
    private final KieServices kieServices = KieServices.Factory.get();

    @Bean
    @ConditionalOnMissingBean
    public KieFileSystem kieFileSystem() throws IOException {
        System.setProperty("drools.dateformat", "yyyy-MM-dd");
        KieFileSystem kieFileSystem = kieServices.newKieFileSystem();
        ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
        Resource[] files = resourcePatternResolver.getResources("classpath*:" + RULES_PATH + "*.*");
        String path;
        for (Resource file : files) {
            path = RULES_PATH + file.getFilename();
            kieFileSystem.write(ResourceFactory.newClassPathResource(path, "UTF-8"));
        }
        return kieFileSystem;
    }

    @Bean
    @ConditionalOnMissingBean
    public KieContainer kieContainer() throws IOException {
        KieRepository kieRepository = kieServices.getRepository();
        kieRepository.addKieModule(kieRepository::getDefaultReleaseId);
        KieBuilder kieBuilder = kieServices.newKieBuilder(kieFileSystem());
        kieBuilder.buildAll();
        return kieServices.newKieContainer(kieRepository.getDefaultReleaseId());
    }

    @Bean
    @ConditionalOnMissingBean
    public KieBase kieBase() throws IOException {
        return kieContainer().getKieBase();
    }

    @Bean
    @ConditionalOnMissingBean
    public KModuleBeanFactoryPostProcessor kiePostProcessor() {
        return new KModuleBeanFactoryPostProcessor();
    }
}

**第六步:创建RuleService类 **

package com.example.drools.service;


import com.example.drools.entity.Order;
import org.drools.core.base.RuleNameEqualsAgendaFilter;
import org.drools.core.base.RuleNameStartsWithAgendaFilter;
import org.kie.api.KieBase;
import org.kie.api.runtime.KieSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class RuleService {
    @Autowired
    private KieBase kieBase;

    /**
     * 执行订单金额积分规则
     */
    public Order executeOrderRule(Order order) {
        KieSession kieSession = kieBase.newKieSession();
        kieSession.insert(order);
        kieSession.fireAllRules(new RuleNameStartsWithAgendaFilter("score_"));
        kieSession.dispose();
        return order;
    }
} 

第七步:创建OrderController类

package com.example.drools.controller;


import com.example.drools.entity.Order;
import com.example.drools.service.RuleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class OrderController {
    @Autowired
    private RuleService ruleService;

    @GetMapping("/saveOrder")
    public Order saveOrder(Order order) {
        System.out.println("hello,world");
        return ruleService.executeOrderRule(order);
    }
}

第八步:新建order.html页面,发送ajax请求(将页面放在src/main/resources/static下,可以在浏览器直接访问,这是springboot默认的静态资源文件夹)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>订单页面</title>
    <script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
    <style>
        div {
            margin: auto;
            width: 500px;
        }

        #score {
            color: red;
            font-size: 14px;
        }
    </style>
</head>
<body>
<div>
    <table>
        <tr>
            <td>订单金额:</td>
            <td><input type="text" id="amout" name="amout"/></td>
        </tr>
        <tr>
            <td>该订单可以累计的积分:</td>
            <td><span id="score"></span></td>
        </tr>
    </table>
</div>
</body>
<script type="text/javascript">
    $(function () {
        $("#amout").blur(function () {
            $.ajax({
                url: "/saveOrder",
                type: "get",
                data: {"amout": $("#amout").val()},
                dataType: "json",
                success: function (data) {
                    //设置积分
                    $("#score").html(data.score);
                }
            });
        });
    });
</script>
</html>

最终效果: 访问http://127.0.0.1:8088/order.html