Featured image of post 规则引擎

规则引擎

一、简介

1. 什么是 Drools

Drools 是一款由 JBoss 组织提供的基于 Java 的开源规则引擎,属于业务规则管理系统(BRMS)。其核心思想是将业务规则从硬编码中分离,以规则脚本形式存储(如文件、数据库),支持动态配置与更新,无需修改代码或重启服务即可生效,适用于业务规则复杂且多变的场景(如电商优惠规则、金融风控、社保审批等)。

2. 核心组件

Drools 规则引擎主要由三部分构成:

  • Working Memory(工作内存):存储业务数据(Fact 对象),规则引擎从这里获取数据并与规则匹配。

  • Rule Base(规则库):存放所有定义的业务规则,规则加载后会被解析并存储到规则库中。

  • Inference Engine(推理引擎)

    :核心执行模块,包含:

    • Pattern Matcher(匹配器):将规则库中的规则与工作内存中的 Fact 对象进行模式匹配。
    • Agenda(议程):存放匹配成功的规则,按优先级排序等待执行。
    • Execution Engine(执行引擎):执行议程中排序后的规则。

3. 关键概念

  • Fact(事实):普通 Java Bean 插入工作内存后的对象,是应用与规则引擎的数据交互桥梁。
  • DRL(Drools Rule Language):Drools 规则定义语言,用于编写规则脚本,支持声明式语法。
  • KIE(Knowledge Is Everything):Drools 生态的核心框架,提供规则的编译、部署、执行等统一 API(如 KieSession、KieContainer 等)。

二、创建项目

  1. 依赖

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.0.5</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    
    <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.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>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    
  2. 配置类

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    
    @Configuration
    public class DroolsConfig {
    
        private static final KieServices kieServices = KieServices.Factory.get();
        //制定规则文件的路径
        private static final String RULES_CUSTOMER_RULES_DRL = "rules/orderScore.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;
        }
    
    }
    
  3. Order事实类

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    
    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;
        }
    }
    
  4. orderScore.drl规则文件(resources/rules目录下)

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    
    //订单积分规则
    package com.order
    import com.atguigu.drools.model.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
    
  5. 测试

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    
    @SpringBootTest
    class DroolsDemosApplicationTests {
    
        @Autowired
        private KieContainer kieContainer;
    
        @Test
        public void test(){
            //从Kie容器对象中获取会话对象
            KieSession session = kieContainer.newKieSession();
    
            //Fact对象,事实对象
            Order order = new Order();
            order.setAmout(1300);
    
            //将Order对象插入到工作内存中
            session.insert(order);
    
            //激活规则,由Drools框架自动进行规则匹配,如果规则匹配成功,则执行当前规则
            session.fireAllRules();
            //关闭会话
            session.dispose();
    
            System.out.println("订单金额:" + order.getAmout() +
                    ",添加积分:" + order.getScore());
        }
    
    }
    

三、语法

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

规则体语法结构

1
2
3
4
5
6
7
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:关键字,表示一个规则结束。

注释

在drl形式的规则文件中使用注释和Java类中使用注释一致,分为单行注释和多行注释。

单行注释用"//“进行标记,多行注释以”/“开始,以”/“结束。如下示例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
//规则rule1的注释,这是一个单行注释
rule "rule1"
    when
    then
        System.out.println("rule1触发");
end

/*
规则rule2的注释,
这是一个多行注释
*/
rule "rule2"
    when
    then
        System.out.println("rule2触发");
end

前面我们已经知道了Drools中的匹配器可以将Rule Base中的所有规则与Working Memory中的Fact对象进行模式匹配,那么我们就需要在规则体的LHS部分定义规则并进行模式匹配。LHS部分由一个或者多个条件组成,条件又称为pattern。

pattern的语法结构为:绑定变量名:Object(Field约束)

其中绑定变量名可以省略,通常绑定变量名的命名一般建议以$开始。如果定义了绑定变量名,就可以在规则体的RHS部分使用此绑定变量名来操作相应的Fact对象。Field约束部分是需要返回true或者false的0个或多个表达式。

例如我们的入门案例中:

1
2
3
4
5
6
7
8
//规则二: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

通过上面的例子我们可以知道,匹配的条件为:

1、工作内存中必须存在Order这种类型的Fact对象—–类型约束

2、Fact对象的amout属性值必须大于等于100——属性约束

3、Fact对象的amout属性值必须小于100——属性约束

以上条件必须同时满足当前规则才有可能被激活。

比较操作符

Drools提供的比较操作符,如下表:

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

前6个比较操作符和Java中的完全相同。

Drools内置方法

规则文件的RHS部分的主要作用是通过插入,删除或修改工作内存中的Fact数据,来达到控制规则引擎执行的目的。Drools提供了一些方法可以用来操作工作内存中的数据,**操作完成后规则引擎会重新进行相关规则的匹配,**原来没有匹配成功的规则在我们修改数据完成后有可能就会匹配成功了。

update方法

update方法的作用是更新工作内存中的数据,并让相关的规则重新匹配。 (要避免死循环)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
//规则一:100元以下 不加分
rule "order_rule_1"
    when
        $order:Order(amout < 100)
    then
        $order.setAmout(150);
	    update($order) //update方法用于更新Fact对象,会导致相关规则重新匹配
        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
insert方法

insert方法的作用是向工作内存中插入数据,并让相关的规则重新匹配。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
//规则一:100元以下 不加分
rule "order_rule_1"
    when
        $order:Order(amout < 100)
    then
        Order order = new Order();
        order.setAmout(130);
        insert(order);      //insert方法的作用是向工作内存中插入Fact对象,会导致相关规则重新匹配
        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
retract方法

retract方法的作用是删除工作内存中的数据,并让相关的规则重新匹配。

1
2
3
4
5
6
7
8
//规则一:100元以下 不加分
rule "order_rule_1"
    when
        $order:Order(amout < 100)
    then
        retract($order)      //retract方法的作用是删除工作内存中的Fact对象,会导致相关规则重新匹配
        System.out.println("成功匹配到规则一:100元以下 不加分");
end

规则属性 attributes

前面我们已经知道了规则体的构成如下:

1
2
3
4
5
6
7
rule "ruleName"
    attributes
    when
        LHS
    then
        RHS
end

本章节就是针对规则体的attributes属性部分进行讲解。Drools中提供的属性如下表(部分属性):

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

重点说一下我们项目需要使用的属性

salience属性

salience属性用于指定规则的执行优先级,取值类型为Integer数值越大越优先执行。每个规则都有一个默认的执行顺序,如果不设置salience属性,规则体的执行顺序为由上到下。

可以通过创建规则文件salience.drl来测试salience属性,内容如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
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

通过控制台可以看到,由于以上三个规则没有设置salience属性,所以执行的顺序是按照规则文件中规则的顺序由上到下执行的。接下来我们修改一下文件内容:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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

通过控制台可以看到,规则文件执行的顺序是按照我们设置的salience值由大到小顺序执行的。

建议在编写规则时使用salience属性明确指定执行优先级。

no-loop属性

no-loop属性用于防止死循环,当规则通过update之类的函数修改了Fact对象时,可能使当前规则再次被激活从而导致死循环。取值类型为Boolean,默认值为false,测试步骤如下:

编写规则文件/resources/rules/activationgroup.drl

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
//订单积分规则
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

通过控制台可以看到,由于我们没有设置no-loop属性的值,所以发生了死循环。接下来设置no-loop的值为true再次测试则不会发生死循环。

global全局变量

global关键字用于在规则文件中定义全局变量,它可以让应用程序的对象在规则文件中能够被访问。可以用来为规则文件提供数据或服务。

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

在使用global定义的全局变量时有两点需要注意:

1、如果对象类型为包装类型时,在一个规则中改变了global的值,那么只针对当前规则有效,对其他规则中的global不会有影响。可以理解为它是当前规则代码中的global副本,规则内部修改不会影响全局的使用。

2、如果对象类型为集合类型或JavaBean时,在一个规则中改变了global的值,对java代码和所有规则都有效。

订单Order:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
package com.atguigu.drools.model;

public class Order {

    private double amout;

    public double getAmout() {
        return amout;
    }

    public void setAmout(double amout) {
        this.amout = amout;
    }

}

积分Integral:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
package com.atguigu.drools.model;

public class Integral {

    private double score;

    public double getScore() {
        return score;
    }

    public void setScore(double score) {
        this.score = score;
    }
}

规则文件:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
//订单积分规则
package com.order
import com.atguigu.drools.model.Order

global com.atguigu.drools.model.Integral integral;

//规则一:100元以下 不加分
rule "order_rule_1"
    no-loop true         //防止陷入死循环
    when
        $order:Order(amout < 100)
    then
        integral.setScore(10);
        update($order)
        System.out.println("成功匹配到规则一:100元以下 不加分");
end

测试:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Test
public void test1(){
    //从Kie容器对象中获取会话对象
    KieSession session = kieContainer.newKieSession();

    //Fact对象,事实对象
    Order order = new Order();
    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());
    System.out.println("添加积分:" + integral.getScore());
}
使用 Hugo 构建
主题 StackJimmy 设计

发布了 16 篇文章 | 共 31507 字