【Drools规则引擎】

1、什么是规则引擎

规则引擎,全称为业务规则管理系统,英文名为BRMS(即Business Rule Management System)。规则引擎的主要思想是将应用程序中的业务决策部分分离出来,并使用预定义的语义模板编写业务决策(业务规则),由用户或开发者在需要时进行配置、管理。

需要注意的的规则引擎并不是一个具体的技术框架,而是指的一类系统,即业务规则管理系统。目前市面上具体的规则引擎产品有:drools、VisualRules、iLog等。

规则引擎实现了将业务决策从应用程序代码中分离出来,接收数据输入,解释业务规则,并根据业务规则做出业务决策。规则引擎其实就是一个输入输出平台。

2、Drools介绍

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

drools官网:https://www.drools.org/

drools中文网:Drools中文网 | 基于java的功能强大的开源规则引擎

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

在项目中使用drools时,既可以单独使用也可以整合spring使用。如果单独使用只需导入如下maven坐标即可:

<dependency>
    <groupId>org.drools</groupId>
    <artifactId>drools-compiler</artifactId>
    <version>7.6.0.Final</version>
</dependency>

3、Drools入门案例

1、业务场景说明

业务场景:消费者在图书商城购买图书,下单后需要在支付页面显示订单优惠后的价格。具体优惠规则如下:

现在需要根据上面的规则计算优惠后的价格。

2、开发实现

第一步:创建maven工程并导入drools相关maven坐标

        <!-- drools规则引擎 -->
        <dependency>
            <groupId>org.drools</groupId>
            <artifactId>drools-compiler</artifactId>
            <version>7.6.0.Final</version>
        </dependency>
 
        <!-- junit单元测试 -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>

第二部:根据drools要求创建resources/META-INF/kmodule.xml

<?xml version="1.0" encoding="UTF-8"?>
<kmodule xmlns="http://jboss.org/kie/6.0.0/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.ws.soon.entity;
 
import lombok.Data;
 
/**
 * 订单
 */
@Data
public class Order {
    private Double  originalPrice; // 订单原始价格,即优惠前的价格
    private Double realPrice; // 订单真实价格,即优惠后的价格
}

第四步:创建规则文件resources/rules/bookDiscount.drl

// 图书优惠规则
package book.discount
import com.ws.soon.entity.Order
 
// 规则一:所购图书总价在100元以下的没有优惠
rule "book_discount_1"
    when
        $order: Order(originalPrice < 100) // 匹配模式,到规则引擎中(工作内存)查找Order对象,命名为$order
    then
        $order.setRealPrice($order.getOriginalPrice());
        System.out.println("成功匹配到规则一,所购图书总价在100元以下无优惠");
end
 
// 规则二:所购图书总价在100~200的优惠20元
rule "book_discount_2"
    when
        $order: Order(originalPrice >= 100 && originalPrice < 200)
    then
        $order.setRealPrice($order.getOriginalPrice() - 20);
        System.out.println("成功匹配到规则二,所购图书总价在100~200元之间");
end
 
// 规则三:所购图书总价在200~300元的优惠50元
rule "book_discount_3"
    when
        $order: Order(originalPrice >= 200 && originalPrice < 300)
    then
        $order.setRealPrice($order.getOriginalPrice() - 50);
        System.out.println("成功匹配到规则三,所购图书总价在200~300元之间");
end
 
// 规则四:所购图书总价在300元及以上的优惠100元
rule "book_discount_4"
    when
        $order: Order(originalPrice >= 300)
    then
        $order.setRealPrice($order.getOriginalPrice() - 100);
        System.out.println("成功匹配到规则四,所购图书总价在300元及以上");
end
 
 

第五步:编写单元测试

package com.ws.soon.test;
 
import com.ws.soon.entity.Order;
import org.junit.Test;
import org.kie.api.KieServices;
import org.kie.api.runtime.KieContainer;
import org.kie.api.runtime.KieSession;
 
public class DroolsTest {
 
    @Test
    public void test() {
        KieServices kieServices = KieServices.Factory.get();
        // 获取Kie容器对象(默认容器对象
        KieContainer kieContainer = kieServices.newKieClasspathContainer();
        // 从Kie容器对象中获取会话对象(默认session对象
        KieSession kieSession = kieContainer.newKieSession();
 
        Order order = new Order();
        order.setOriginalPrice(160d);
 
        // 将order对象插入工作内存
        kieSession.insert(order);
 
        System.out.println("匹配规则前优惠后价格:" + order.getRealPrice());
 
        // 匹配对象
        // 激活规则,由drools框架自动进行规则匹配。若匹配成功,则执行
        kieSession.fireAllRules();
 
        // 关闭会话
        kieSession.dispose();
 
        System.out.println("优惠前价格:" + order.getOriginalPrice() + "\n优惠后价格:" + order.getRealPrice());
    }
}

控制台输出如下:

通过上面的入门案例可以发现,使用drools引擎规则主要工作就是编写规则文件,在规则文件中定义和业务相关的业务规则,例如本案例定义的就是图书的优惠规则。规则定义好后就需要调用drools提供的api将数据提供给规则引擎进行规则模式匹配,规则引擎会执行匹配成功的规则,并将计算的结果返回。

可能大家会有疑问,虽然没有在代码中编写规则的判断逻辑,但是还是在规则文件中编写了业务规则,这跟在代码中编写规则有什么本质的区别?

前面已经提到,使用规则引擎时,规则可以做到动态管理。业务人员可以像管理数据一样对业务规则进行管理,比如查询、添加、更新、统计、提交业务规则等。这样就可以做到在不重启服务的情况下调整业务规则。

4、小结

1、规则引擎构成

drools规则引擎由以下三部分构成:

  • Working Memory(工作内存)

  • Rule Base(规则库)

  • Inference Engine(推理引擎)

其中Inference Engine(推理引擎)又包括:

  • Pattern Matcher(匹配器)

  • Agenda(议程)

  • Execution Engine(执行引擎)

如下图所示:

2、相关概念说明

Working Memory:工作内存,drools规则引擎会从Working Memory中获取数据并和规则文件中定义的规则进行模式匹配,所以我们开发的应用程序只需要将我们的数据插入到Working Memory中即可,例如本案例中我们调用kieSession.insert(order);就是将order对象插入到工作内存中。

Fact:事实,是指在drools规则应用当中,将一个普通的javaBean插入到Working Memory后的对象就是Fact对象,例如本案例中的Order对象就属于Fact对象。Fact对象是我们的应用和规则引擎进行交互的桥梁或通道。

Rule Base:规则库,我们在规则文件中定义的规则都会被加载到规则库中。

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

Agenda:议程,用于存放通过匹配器进行模式匹配后被激活的规则。

Execution Engine:执行引擎,执行Agenda中被激活的规则。

3、KIE介绍

我们在操作Drools时经常使用的API以及他们之间的关系如下图:

通过上面的API可以发现,大部分类都是以Kie开头。Kie全称为Knowledge is Everything,即“只是就是一切”的缩写,是Jboss一系列项目的总称。如下图所示,Kie的主要模块有OptaPlanner、Drools、UberFire、jBPM。

通过上图可以看到,Drools是整个KIE项目中的一个组件,Drools中还包括一个Drools-WB的模块,他是一个可视化的规则编辑器。

5、Drools基础语法

1、规则文件构成

在使用Drools时非常重要的一个工作就是编写规则文件,通常规则文件的后缀为.drl。

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

一套完整的规则文件内容构成如下:

关键字

描述

package

包名,只限于逻辑上的管理,同一个包名下的查询或者函数可以直接调用

import

用于导入类或静态方法

global

全景变量

function

自定义函数

query

查询

rule...end

规则体

Drools支持的规则文件,除了drl形式,还有Excel文件类型的。

2、规则体语法结构

规则体是规则文件中的重要组成部分,是进行业务规则判断、处理业务结果的部分。

规则语法结构如下:

rule "ruleName" 
    attributes 
    when 
        LHS
    then 
        RHS
end 

rule:关键字,表示规则开始,参数为规则的唯一名称。

attribute:规则属性,是rule与when之间的参数,为可选项。

when:关键字,后面跟规则的条件部分。

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

then:关键字,后面跟规则的结果部分。

RHS(Right Hand Side):是规则的后果或行动部分的通用名称。

end:关键字,表示一个规则的结束。

3、注释

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

单行注释用”//"进行标记,多行注释以"/*"开始,以"*/"结束。

4、Pattern匹配规则

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

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

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

例如我们的入门案例中:

// 规则二:所购图书总价在100~200的优惠20元
rule "book_discount_2"
    when
        // Order为类型约束,originalPrice为属性约束
        $order: Order(originalPrice >= 100 && originalPrice < 200)
    then
        $order.setRealPrice($order.getOriginalPrice() - 20);
        System.out.println("成功匹配到规则二,所购图书总价在100~200元之间");
end

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

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

  1. Fact对象的originalPrice属性值必须小于200----属性约束

  1. Fact对象的originalPrice属性值必须大于等 于1-0----属性约束

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

绑定变量既可以用在对象上,也可以用在对象的属性上。例如上面的例子可以改为:

// 规则二:所购图书总价在100~200的优惠20元
rule "book_discount_2"
    when
        $order: Order($op:originalPrice >= 100 && originalPrice < 200)
    then
        System.out.println("$op=" + $op);
        $order.setRealPrice($order.getOriginalPrice() - 20);
        System.out.println("成功匹配到规则二,所购图书总价在100~200元之间");
end

LHS部分还可以定义多个pattern,多个pattern之间可以使用and或者or进行连接,也可以写,默认连接为and。

5、比较操作符

符号

说明

>

大于

<

小于

>=

大于等于

<=

小于等于

==

等于

!=

不等于

contains

检查一个Fact对象的某个属性值是否包含一个指定的对象值

not contains

检查一个Fact对象的某个属性值是否不包含一个指定的对象值

memberOf

判断一个Fact对象的某个属性是否在一个或多个集合中

not memberOf

判断一个Fact对象的某个属性是否不在一个或多个集合中

matches

判断一个Fact对象的属性是否与提供的标准的Java正则表达式进行匹配

not matches

判断一个Fact对象的属性是否不与提供的标准的Java正则表达式进行匹配

前6个比较操作符和Java中的完全相同,下面我们重点学习后6个比较操作符。

1、语法

●contains | not contains语法结构    // 包含与不包含,是模糊匹配,类似于sql的like
Object(Field[Collection/Array] contains value)
Object(Field[Collection/Array] npt contains value)
 
●memberOf | not memberOf语法结构
Object(field memberOf value[Collection/Array])
Object(field not memberOf value[Collection/Array1)
 
●matches | not matches语法结构
Object(field matches "正则表达式")
Object(field not matches "正则表达式")

2、操作步骤

第一步:创建实体类,用于测试比较运算符

package com.ws.soon.entity;
 
import lombok.Data;
 
import java.util.List;
 
/**
 * @author wangsaisoon
 * @Description 实体类 用户测试比较运算符
 * @date 2021/10/27 21:23
 * @Version 1.0
 */
@Data
public class ComparisonOperatorEntity {
    private String names;
    private List<String> list;
}

第二步:创建规则文件resources/rules/comparisonOperator.drl

package comparisonOperator
 
import com.ws.soon.entity.ComparisonOperatorEntity
 
/**
 * 当前规则用于测试drools提供的操作运算符
 */
 
// 测试比较操作符contains
rule "rule_comparison_contains"
    when
        ComparisonOperatorEntity(names contains "王小白") or
        ComparisonOperatorEntity(list contains names)
    then
        System.out.println("规则:rule_comparison_contains触发了...");
end
 
// 测试比较操作符contains
rule "rule_comparison_not_contains"
    when
        ComparisonOperatorEntity(names not contains "王小白") and
        ComparisonOperatorEntity(list not contains names)
    then
        System.out.println("规则:rule_comparison_not_contains触发了...");
end
 
// 测试比较操作符memberOf
rule "rule_comparison_memberOf"
    when
        ComparisonOperatorEntity(names memberOf list)
    then
        System.out.println("规则:rule_comparison_memberOf触发了...");
end
 
// 测试比较操作符not memberOf
rule "rule_comparison_not_memberOf"
    when
        ComparisonOperatorEntity(names not memberOf list)
    then
        System.out.println("规则:rule_comparison_not_memberOf触发了...");
end
 
// 测试比较操作符matches
rule "rule_comparison_matches"
    when
        ComparisonOperatorEntity(names matches "王.*") // 正则表达式
    then
        System.out.println("规则:rule_comparison_matches触发了...");
end
 
// 测试比较操作符not matches
rule "rule_comparison_not_matches"
    when
        ComparisonOperatorEntity(names not matches "王.*") // 正则表达式
    then
        System.out.println("规则:rule_comparison_not_matches触发了...");
end

第三步:编写单元测试

package com.ws.soon.test;
 
import com.ws.soon.entity.ComparisonOperatorEntity;
import org.junit.Test;
import org.kie.api.KieServices;
import org.kie.api.runtime.KieContainer;
import org.kie.api.runtime.KieSession;
 
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
 
public class ComparisonOperatorEntityTest {
 
    @Test
    public void test() {
        KieServices kieServices = KieServices.Factory.get();
        // 获取Kie容器对象(默认容器对象
        KieContainer kieContainer = kieServices.newKieClasspathContainer();
        // 从Kie容器对象中获取会话对象(默认session对象
        KieSession kieSession = kieContainer.newKieSession();
 
        ComparisonOperatorEntity fact = new ComparisonOperatorEntity();
        String names = "1王2小白";
        fact.setNames(names);
 
        List<String> list = new ArrayList<String>();
        list.add("小黑");
        list.add("小白");
//        list.add(names);
        fact.setList(list);
        // 将order对象插入工作内存
        kieSession.insert(fact);
 
        // 匹配对象
        // 激活规则,由drools框架自动进行规则匹配。若匹配成功,则执行
        kieSession.fireAllRules();
 
        // 关闭会话
        kieSession.dispose();
 
    }
}

6、执行指定规则

在咱们编写drools规定文件的过程中,可能会编写好多规定。drools引擎在模式匹配的时候,可能一下子激活了好多规定,然而我只想执行某个指定的规定,那么这个时候该怎么操作呢?

在drools模式匹配的时候,会将所有的规定进行匹配,匹配胜利的规定会放入到Agenda(议程)中,而fireAllRules(AgendaFilter)办法,能够传递一个AgendaFilter对Agenda中的激活的规定进行过滤。

应用entry-point能够定义模式的数据源对应的入口点或事件流。

咱们存在一个Api(api,invokedCnt)对象

规定一:工作内存中存在Api对象,且属性api=="/users/info"。

规定二:工作内存中存在Api对象,且属性invokedCnt > 10。

咱们向工作内存中插入一个 Api("/users/info",100),此时规定一和规定二都会匹配到,然而我只想执行规定二。

Api(api == "/users/info" ) from entry-point "first-entry-point"
Api api = new Api("/users/info", 100);
EntryPoint entryPoint = kieSession.getEntryPoint("second-entry-point");
entryPoint.insert(api);
kieSession.fireAllRules();

drl文件:

package rules

import com.huan.drools.Api

rule "rule_agenda_filter_01"
    when
        $api: Api(api == "/users/info" )
    then
        System.out.println("以后执行的规定是: " + drools.getRule().getName());
end

rule "rule_agenda_filter_02"
    when
        $api: Api(invokedCnt > 10)
    then
        System.out.println("以后执行的规定是: " + drools.getRule().getName());
end

java代码:

Api api = new Api("/users/info", 100);
kieSession.insert(api);

// 所有模式匹配胜利后的规定回进入到agenda中,而后通过AgendaFilter过滤出须要执行的规定
kieSession.fireAllRules(new AgendaFilter() {
    @Override
    public boolean accept(Match match) {
        String ruleName = match.getRule().getName();
        return Objects.equals(ruleName, "rule_agenda_filter_02");
    }
});

能够看到此处通过AgendaFilter进行了规定的过滤,只执行rule_agenda_filter_02规定。

6、Drools内置方法

  • update方法

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

package student
import drools.entity.Student
​
/*
 当前规则文件用于测试Drools提供的内置方法
*/
​
rule "rule_student_age小于10岁"
    when
        $s:Student(age < 10)
    then
        $s.setAge(15);
        update($s);//更新数据,导致相关的规则会重新匹配
        System.out.println("规则rule_student_age小于10岁触发");
end
​
rule "rule_student_age小于20岁同时大于10岁"
    when
        $s:Student(age < 20 && age > 10)
    then
        $s.setAge(25);
        update($s);//更新数据,导致相关的规则会重新匹配
        System.out.println("规则rule_student_age小于20岁同时大于10岁触发");
end
​
rule "rule_student_age大于20岁"
    when
        $s:Student(age > 20)
    then
        System.out.println("规则rule_student_age大于20岁触发");
end
  • insert方法

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

package student
import drools.entity.Student
​
/*
 当前规则文件用于测试Drools提供的内置方法
*/
​
rule "rule_student_age等于10岁"
    when
        $s:Student(age == 10)
    then
        Student student = new Student();
        student.setAge(5);
        insert(student);//插入数据,导致相关的规则会重新匹配
        System.out.println("规则rule_student_age等于10岁触发");
end
​
rule "rule_student_age小于10岁"
    when
        $s:Student(age < 10)
    then
        $s.setAge(15);
        update($s);
        System.out.println("规则rule_student_age小于10岁触发");
end
​
rule "rule_student_age小于20岁同时大于10岁"
    when
        $s:Student(age < 20 && age > 10)
    then
        $s.setAge(25);
        update($s);
        System.out.println("规则rule_student_age小于20岁同时大于10岁触发");
end
​
rule "rule_student_age大于20岁"
    when
        $s:Student(age > 20)
    then
        System.out.println("规则rule_student_age大于20岁触发");
end
  • retract方法

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

package student
import drools.entity.Student
​
/*
 当前规则文件用于测试Drools提供的内置方法
*/
​
rule "rule_student_age等于10岁时删除数据"
    /*
    salience:设置当前规则的执行优先级,数值越大越优先执行,默认值为0.
    因为当前规则的匹配条件和下面规则的匹配条件相同,为了保证先执行当前规则,需要设置优先级
    */
    salience 100 
    when
        $s:Student(age == 10)
    then
        retract($s);//retract方法的作用是删除工作内存中的数据,并让相关的规则重新匹配。
        System.out.println("规则rule_student_age等于10岁时删除数据触发");
end
​
rule "rule_student_age等于10岁"
    when
        $s:Student(age == 10)
    then
        Student student = new Student();
        student.setAge(5);
        insert(student);
        System.out.println("规则rule_student_age等于10岁触发");
end
​
rule "rule_student_age小于10岁"
    when
        $s:Student(age < 10)
    then
        $s.setAge(15);
        update($s);
        System.out.println("规则rule_student_age小于10岁触发");
end
​
rule "rule_student_age小于20岁同时大于10岁"
    when
        $s:Student(age < 20 && age > 10)
    then
        $s.setAge(25);
        update($s);
        System.out.println("规则rule_student_age小于20岁同时大于10岁触发");
end
​
rule "rule_student_age大于20岁"
    when
        $s:Student(age > 20)
    then
        System.out.println("规则rule_student_age大于20岁触发");
end

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值