【Java】规则引擎 Drools

https://www.bilibili.com/video/BV1nW421R7qJ 来自尚硅谷

背景

在这里插入图片描述

/**
 * 设置订单积分
 */
public void setOrderPoint(Order order){
    if (order.getAmout() <= 100){
        order.setScore(0);
    }else if(order.getAmout() > 100 && order.getAmout() <= 500){
        order.setScore(100);
    }else if(order.getAmout() > 500 && order.getAmout() <= 1000){
        order.setScore(500);
    }else{
        order.setScore(1000);
    }
}

对于不经常变化的业务,我们通常是将规则硬编码到程序中。但是经常变化的业务,我们就得把业务流程从代码中剥离出来。

1、硬编码实现业务规则难以维护
2、硬编码实现业务规则难以应对变化
3、业务规则发生变化需要修改代码,重启服务后才能生效

那么面对上面的业务场景,还有什么好的实现方式吗?

此时我们需要引入规则引擎来帮助我们将规则从代码中分离出去,让开发人员从规则的代码逻辑中解放出来,把规则的维护和设置交由业务人员去管理。

规则引擎概述

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

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

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

系统中引入规则引擎后,业务规则不再以程序代码的形式驻留在系统中,取而代之的是处理规则的规则引擎,业务规则存储在规则库中,完全独立于程序。业务人员可以像管理数据一样对业务规则进行管理,比如查询、添加、更新、统计、提交业务规则等。业务规则被加载到规则引擎中供应用系统调用。

对于一些存在比较复杂的业务规则并且业务规则会频繁变动的系统比较适合使用规则引擎,如下:
1、风险控制系统----风险贷款、风险评估
2、反欺诈项目----银行贷款、征信验证
3、决策平台系统----财务计算
4、促销平台系统----满减、打折、加价购

Drools

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

入门案例

引入依赖

   <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.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </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>
    </dependencies>

添加Drools配置类 (固定的),规则文件所在路径一定要指定对

import org.kie.api.KieServices;
import org.kie.api.builder.*;
import org.kie.api.runtime.KieContainer;
import org.kie.api.runtime.KieSession;
import org.kie.internal.io.ResourceFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * 规则引擎配置类
 */
@Configuration
public class DroolsConfig {

    private static final KieServices kieServices = KieServices.Factory.get();
    //制定规则文件的路径
    private static final String RULES_CUSTOMER_RULES_DRL = "rules/order.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;
    }

}

创建实体类Order

import lombok.Data;

@Data
public class Order {
    private double amout;
    private double score;
}

lombok指定好版本很重要

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.10</version>
        </dependency>

创建规则文件resources/rules/order.drl

//订单积分规则
package com.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

编写测试类

package com.atguigu.drools;

import org.junit.jupiter.api.Test;
import com.atguigu.drools.model.Order;
import org.kie.api.runtime.KieContainer;
import org.kie.api.runtime.KieSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@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  com.atguigu.daijia

import com.atguigu.daijia.model.form.rules.FeeRuleRequest;
import java.math.BigDecimal;
import java.math.RoundingMode;

global com.atguigu.daijia.model.vo.rules.FeeRuleResponse feeRuleResponse;

/**
1.起步价
    00:00:00-06:59:59  19元(含3公里)
    07:00:00-23:59:59  19元(含5公里)
*/
rule "起步价 00:00:00-07:00:00  19元(含3公里)"
    salience 10          //指定优先级,数值越大优先级越高,不指定的情况下由上到下执行
    no-loop true         //防止陷入死循环
    when
        $rule:FeeRuleRequest(startTime >= "00:00:00" && startTime < "07:00:00")
    then
        //基础里程 3公里
        feeRuleResponse.setBaseDistance(new BigDecimal("3.0"));
        //收费19元 础里程费
        feeRuleResponse.setBaseDistanceFee(new BigDecimal("19.0"));
        //超出里程  超出基础里程的里程
        feeRuleResponse.setExceedDistance(new BigDecimal("0.0"));
        feeRuleResponse.setExceedDistancePrice(new BigDecimal("4.0"));
        System.out.println("00:00:00-07:00:00 " + feeRuleResponse.getBaseDistance() + "公里,起步价:" + feeRuleResponse.getBaseDistanceFee() + "元");
end

rule "起步价 07:00:00-23:59:59  19元(含5公里)"
    salience 10          //指定优先级,数值越大优先级越高,不指定的情况下由上到下执行
    no-loop true         //防止陷入死循环
    when
        /*规则条件,到工作内存中查找FeeRuleRequest对象
        里面出来的结果只能是ture或者false
        $rule是绑定变量名,可以任意命名,官方推荐$符号,定义了绑定变量名,可以在then部分操作fact对象*/
        $rule:FeeRuleRequest(startTime >= "07:00:00" && startTime <= "23:59:59")
    then
        feeRuleResponse.setBaseDistance(new BigDecimal("5.0"));
        feeRuleResponse.setBaseDistanceFee(new BigDecimal("19.0"));

        //5公里内里程费为0
        feeRuleResponse.setExceedDistance(new BigDecimal("0.0"));
        feeRuleResponse.setExceedDistancePrice(new BigDecimal("3.0"));
        System.out.println("07:00:00-23:59:59 " + feeRuleResponse.getBaseDistance() + "公里,起步价:" + feeRuleResponse.getBaseDistanceFee() + "元");
end

/**
2.里程费
    超出起步里程后开始计算
    00:00:00-06:59:59   4元/1公里
    07:00:00-23:59:59   3元/1公里
*/
rule "里程费 00:00:00-06:59:59 4元/1公里"
    salience 10          //指定优先级,数值越大优先级越高,不指定的情况下由上到下执行
    no-loop true         //防止陷入死循环
    when
        /*规则条件,到工作内存中查找FeeRuleRequest对象
        里面出来的结果只能是ture或者false
        $rule是绑定变量名,可以任意命名,官方推荐$符号,定义了绑定变量名,可以在then部分操作fact对象*/
        $rule:FeeRuleRequest(startTime >= "00:00:00"
            && startTime <= "06:59:59"
            && distance > 3.0)
    then
        BigDecimal exceedDistance = $rule.getDistance().subtract(new BigDecimal("3.0"));
        feeRuleResponse.setExceedDistance(exceedDistance);
        feeRuleResponse.setExceedDistancePrice(new BigDecimal("4.0"));
        System.out.println("里程费,超出里程:" + feeRuleResponse.getExceedDistance() + "公里,单价:" + feeRuleResponse.getExceedDistancePrice());
end
rule "里程费 07:00:00-23:59:59 3元/1公里"
    salience 10          //指定优先级,数值越大优先级越高,不指定的情况下由上到下执行
    no-loop true         //防止陷入死循环
    when
        /*规则条件,到工作内存中查找FeeRuleRequest对象
        里面出来的结果只能是ture或者false
        $rule是绑定变量名,可以任意命名,官方推荐$符号,定义了绑定变量名,可以在then部分操作fact对象*/
        $rule:FeeRuleRequest(startTime >= "07:00:00"
            && startTime <= "23:59:59"
            && distance > 5.0)
    then
        BigDecimal exceedDistance = $rule.getDistance().subtract(new BigDecimal("5.0"));
        feeRuleResponse.setExceedDistance(exceedDistance);
        feeRuleResponse.setExceedDistancePrice(new BigDecimal("3.0"));
        System.out.println("里程费,超出里程:" + feeRuleResponse.getExceedDistance() + "公里,单价:" + feeRuleResponse.getExceedDistancePrice());
end

/**
3.等候费
    等候10分钟后  1元/1分钟
*/
rule "等候费 等候10分钟后 1元/1分钟"
    salience 10          //指定优先级,数值越大优先级越高,不指定的情况下由上到下执行
    no-loop true         //防止陷入死循环
    when
        /*规则条件,到工作内存中查找FeeRuleRequest对象
        里面出来的结果只能是ture或者false
        $rule是绑定变量名,可以任意命名,官方推荐$符号,定义了绑定变量名,可以在then部分操作fact对象*/
        $rule:FeeRuleRequest(waitMinute > 10)
    then
        Integer exceedWaitMinute = $rule.getWaitMinute() - 10;
        feeRuleResponse.setBaseWaitMinute(10);
        feeRuleResponse.setExceedWaitMinute(exceedWaitMinute);
        feeRuleResponse.setExceedWaitMinutePrice(new BigDecimal("1.0"));
        System.out.println("等候费,超出分钟:" + feeRuleResponse.getExceedWaitMinute() + "分钟,单价:" + feeRuleResponse.getExceedWaitMinutePrice());
end
rule "无等候费"
    salience 10          //指定优先级,数值越大优先级越高,不指定的情况下由上到下执行
    no-loop true         //防止陷入死循环
    when
        /*规则条件,到工作内存中查找FeeRuleRequest对象
        里面出来的结果只能是ture或者false
        $rule是绑定变量名,可以任意命名,官方推荐$符号,定义了绑定变量名,可以在then部分操作fact对象*/
        $rule:FeeRuleRequest(waitMinute <= 10)
    then
        feeRuleResponse.setBaseWaitMinute(10);
        feeRuleResponse.setExceedWaitMinute(0);
        feeRuleResponse.setExceedWaitMinutePrice(new BigDecimal("1.0"));
        System.out.println("等候费:无");
end

/**
4.远途费
    订单行程超出12公里后每公里1元
*/
rule "远途费 订单行程超出12公里后每公里1元"
    salience 10          //指定优先级,数值越大优先级越高,不指定的情况下由上到下执行
    no-loop true         //防止陷入死循环
    when
        /*规则条件,到工作内存中查找FeeRuleRequest对象
        里面出来的结果只能是ture或者false
        $rule是绑定变量名,可以任意命名,官方推荐$符号,定义了绑定变量名,可以在then部分操作fact对象*/
        $rule:FeeRuleRequest(distance > 12.0)
    then
        BigDecimal exceedLongDistance = $rule.getDistance().subtract(new BigDecimal("12.0"));
        feeRuleResponse.setBaseLongDistance(new BigDecimal("12.0"));
        feeRuleResponse.setExceedLongDistance(exceedLongDistance);
        feeRuleResponse.setExceedLongDistancePrice(new BigDecimal("1.0"));
        System.out.println("远途费,超出公里:" + feeRuleResponse.getExceedLongDistance() + "公里,单价:" + feeRuleResponse.getExceedLongDistancePrice());
end
rule "无远途费"
    salience 10          //指定优先级,数值越大优先级越高,不指定的情况下由上到下执行
    no-loop true         //防止陷入死循环
    when
        /*规则条件,到工作内存中查找FeeRuleRequest对象
        里面出来的结果只能是ture或者false
        $rule是绑定变量名,可以任意命名,官方推荐$符号,定义了绑定变量名,可以在then部分操作fact对象*/
        $rule:FeeRuleRequest(distance <= 12.0)
    then
        feeRuleResponse.setBaseLongDistance(new BigDecimal("12.0"));
        feeRuleResponse.setExceedLongDistance(new BigDecimal("0"));
        feeRuleResponse.setExceedLongDistancePrice(new BigDecimal("0"));
        System.out.println("远途费:无");
end

/**
5.计算总金额
    订单总金额 = 基础里程费 + 超出基础里程的费 + 等候费 + 远程费
*/
rule "计算总金额"
    salience 10          //指定优先级,数值越大优先级越高,不指定的情况下由上到下执行
    no-loop true         //防止陷入死循环
    when
        /*规则条件,到工作内存中查找FeeRuleRequest对象
        里面出来的结果只能是ture或者false
        $rule是绑定变量名,可以任意命名,官方推荐$符号,定义了绑定变量名,可以在then部分操作fact对象*/
        $rule:FeeRuleRequest(distance > 0.0)
    then
        //订单总金额 = 基础里程费 + 超出基础里程的费 + 等候费 + 远程费
        BigDecimal distanceFee = feeRuleResponse.getBaseDistanceFee().add(feeRuleResponse.getExceedDistance().multiply(feeRuleResponse.getExceedDistancePrice()));
        BigDecimal waitFee = new BigDecimal(feeRuleResponse.getExceedWaitMinute()).multiply(feeRuleResponse.getExceedWaitMinutePrice());
        BigDecimal longDistanceFee = feeRuleResponse.getExceedLongDistance().multiply(feeRuleResponse.getExceedLongDistancePrice());
        
        BigDecimal totalAmount = distanceFee.add(waitFee).add(longDistanceFee);
        feeRuleResponse.setDistanceFee(distanceFee);
        feeRuleResponse.setWaitFee(waitFee);
        feeRuleResponse.setLongDistanceFee(longDistanceFee);
        feeRuleResponse.setTotalAmount(totalAmount);
        System.out.println("计算总金额:" + feeRuleResponse.getTotalAmount() + "元");
end
  • 测试类
/*
 *  1 00:00:00-06:59:59  19元(含3公里)   == 超出12公里
 *  2 00:00:00-06:59:59   4元/1公里      == 增加一公里,单价4元
 *  3 等候10分钟后  1元/1分钟             == 超过10分钟,单价1元
 *  4 订单行程超出12公里后每公里1元        == 超出3公里 ,单价 1元
 * 代驾总金额:19 + 12*4 + 10 + 3 = 80
 */
@Test
void test1() {
    FeeRuleRequest feeRuleRequest = new FeeRuleRequest();
    feeRuleRequest.setDistance(new BigDecimal(15.0));
    feeRuleRequest.setStartTime("01:59:59");
    feeRuleRequest.setWaitMinute(20);

    // 开启会话
    KieSession kieSession = kieContainer.newKieSession();

    FeeRuleResponse feeRuleResponse = new FeeRuleResponse();
    kieSession.setGlobal("feeRuleResponse", feeRuleResponse);
    // 设置订单对象
    kieSession.insert(feeRuleRequest);
    // 触发规则
    kieSession.fireAllRules();
    // 中止会话
    kieSession.dispose();
    System.out.println("后:"+JSON.toJSONString(feeRuleResponse));
}

drools规则引擎原理

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

  • Working Memory(工作内存)
  • Rule Base(规则库)
  • Inference Engine(推理引擎)

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

  • Pattern Matcher(匹配器) 具体匹配哪一个规则,由这个完成
  • Agenda(议程)
  • Execution Engine(执行引擎)

相关概念:

  • 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中被激活的规则。

在这里插入图片描述

规则文件的编写什么的没有放,可以去看一看

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

开心星人

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值