规则引擎 Drools

发布时间 2023-06-21 15:50:59作者: little_lunatic

Drools ,是一款由 JBoss 组织提供的基于 Java 语言编写的一个开源规则引擎,核心思想是把业务规则从程序代码中分离出来,可以更加灵活地管理业务规则。

截屏20230618 232001png

一、什么是规则引擎

规则引擎是一种计算机软件,它根据输入数据和预定义规则,自动执行特定的操作。它可以将复杂的业务逻辑和规则表示为易于理解和修改的形式,并自动化处理这些规则。

二、规则引擎应用场景

业务规则复杂 并且 频繁变动 的系统更适合使用规则引擎。

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

2、反欺诈项目----银行贷款、征信验证

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

4、促销平台系统----满减、消费送积分、打折

// 超市消费送积分活动,v1
// 0 < price <= 100, 积100分
// 100 < price <= 200, 积200分
// 200 < price <= 300, 积300分
程序员小明:if...else... if...else... if...else...... 

老板:积分换东西的人多了,花钱的人变少了,改规则--积分降一个档次

// 超市消费送积分活动,v2
// 0 < price <= 100, 积0分
// 100 < price <= 200, 积100分
// 200 < price <= 300, 积200分
程序员小明:if...else... if...else... if...else...... 

老板:用户积极性变差了,改规则--送的积分增加一点

// 超市消费送积分活动,v3
// 0 < price <= 100, 积50分
// 100 < price <= 200, 积150分
// 200 < price <= 300, 积250分
程序员小明:if...else... if...else... if...else...... 
多少元送多少积分存到数据库,下次规则再变就直接改数据库

老板:业务规则太少,给我加倍!

小明:......

// 超市消费送积分活动,积分翻倍,v4
// 0 < price <= 100, 积50分
// 100 < price <= 200, 积150分
// 200 < price <= 300, 积250分
// 300 < price <= 400, 积350分
// 400 < price <= 500, 积450分
// 500 < price <= 600, 积550分
1.再增加三组if...else...
2.重构代码用责任链模式

程序员小明:什么技术可以将业务规则和代码解耦合?不管规则如何变化,执行端都不用动。
只有规则引擎!

三、Drools 规则引擎的构成

规则引擎由三部分组成

  1. Working Memory(工作内存):存放 fact 对象。

  2. Rule Base(规则库):规则文件中定义的规则都会被加载到规则库。

  3. Interface Engin(推理引擎)

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

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

3.3 Excution Engin(执行引擎):执行Agenda中被激活的规则。

规则引擎执行过程:

在这里插入图片描述

fact(事实):普通的 JavaBean 插入到 Working Memory 后的对象就是 Fact 对象,是应用和规则引擎进行数据交互的桥梁或通道。

drools API 开发完整步骤如下:

截屏20230618 120342png

Kie: knowledge is everything, 是 jBoss 一系列项目共享的一个核心项目,主要目的就是将相关技术整合在一起,其中包含了Drools、OptaPlanner、UberFire、jBPM等技术。

四、Drools 使用方式

在项目中使用 drools 时,既可以单独使用,也可以与 spring boot 整合使用。

4.1 方式一:Drools 单独使用

导入 maven 依赖

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

创建 kmodule.xml

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

<?xml version="1.0" encoding="UTF-8"?>
<kmodule xmlns="http://www.drools.org/xsd/kmodule">

    <kbase name="myKbase1" packages="rules" default="true">
        <ksession name="myKbase1-session" default="true"/>
    </kbase>

</kmodule>

编写 drl (drools rule language)规则文件

一套完整的规则文件结构:

  • package:/src/java/resource目录下的包名

  • import:用于导入类或者静态方法

  • global:全局变量

  • function:自定义函数

  • query:查询

  • rule ... end:规则体

package rules;

import com.kk.model.Order;

// 超市消费送积分活动
// 0 < price <= 100, 积0分
// 100 < price <= 200, 积100分
rule "score-1"
  when
    $order:Order(price>0 && price <= 100)
  then
    System.out.println("触发drools积分规则1:积0分");
    $order.setScore(0);
end

rule "score-2"
  when
    $order:Order(price>100 && price <= 200)
  then
    System.out.println("触发drools积分规则2:积100分");
    $order.setScore(100);
end

测试代码:

    @Test
    public void test() {
        // 1.获取服务对象
        KieServices ks = KieServices.get();
        // 2.通过服务获取容器
        KieContainer container = ks.getKieClasspathContainer();
        // 3.通过容器获取kie session
        KieSession session = container.newKieSession();

        // 定义fact事实对象
        Order order = new Order();
        order.setPrice(120);

        // 5.把fact事实对象插入工作内存
        session.insert(order);
        // 6.激活规则引擎,匹配所有规则,匹配成功会执行规则
        session.fireAllRules();
        // 7.关闭规则引擎
        session.dispose();
    }

测试结果:

截屏20230619 211407png

4.2 方式二:Spring Boot 整合 Drools

导入 maven 依赖

        <dependency>
            <groupId>org.drools</groupId>
            <artifactId>drools-compiler</artifactId>
            <version>${kie.version}</version>
        </dependency>

        <dependency>
            <groupId>org.kie</groupId>
            <artifactId>kie-spring</artifactId>
            <version>${kie.version}</version>
        </dependency>

编写 drl 规则文件

package rules;

import com.kk.model.Order;

// 超市消费送积分活动
// 0 < price <= 100, 积0分
// 100 < price <= 200, 积100分
rule "score-1"
  when
    $order:Order(price>0 && price <= 100)
  then
    System.out.println("触发drools积分规则1:积0分");
    $order.setScore(0);
end

rule "score-2"
  when
    $order:Order(price>100 && price <= 200)
  then
    System.out.println("触发drools积分规则2:积100分");
    $order.setScore(100);
end

编写配置类

import com.kk.utils.KieUtils;
import org.kie.api.KieBase;
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.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.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import java.io.IOException;


@Configuration
public class DroolsAutoConfig {

    // 规则文件存放目录.
    public static final String RULES_PATH = "rules/";

    // 规则库路径.
    public static final String BASE_RULES_PATH = "classpath*:";

    /**
     * 加载指定目录下的规则文件.
     * 
     * @return
     * @throws IOException
     */
    @Bean
    @ConditionalOnMissingBean(KieFileSystem.class)
    public KieFileSystem kieFileSystem() throws IOException {
        KieFileSystem kieFileSystem = KieUtils.getKieServices().newKieFileSystem();
        ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
        Resource[] resources = resourcePatternResolver.getResources(BASE_RULES_PATH + RULES_PATH + "**/*.*");
        for (Resource file : resources) {
            kieFileSystem.write(ResourceFactory.newClassPathResource(RULES_PATH + file.getFilename(), "UTF-8"));
        }
        return kieFileSystem;
    }


    /**
     * kie 容器注册到spring容器.
     *
     * @return
     * @throws IOException
     */
    @Bean
    @ConditionalOnMissingBean(KieContainer.class)
    public KieContainer kieContainer() throws IOException {
        final KieRepository kieRepository = KieUtils.getKieServices().getRepository();

        kieRepository.addKieModule(new KieModule() {
            @Override
            public ReleaseId getReleaseId() {
                return kieRepository.getDefaultReleaseId();
            }
        });

        KieBuilder kieBuilder = KieUtils.getKieServices().newKieBuilder(kieFileSystem());
        kieBuilder.buildAll();

        KieContainer kieContainer = KieUtils.getKieServices().newKieContainer(kieRepository.getDefaultReleaseId());
        KieUtils.setKieContainer(kieContainer);
        return KieUtils.getKieServices().newKieContainer(kieRepository.getDefaultReleaseId());
    }

    /**
     * kieBase 注册到spring容器
     * @return
     * @throws IOException
     */
    @Bean
    @ConditionalOnMissingBean(KieBase.class)
    public KieBase kieBase() throws IOException {
        return kieContainer().getKieBase();
    }

    @Bean
    @ConditionalOnMissingBean(KieSession.class)
    public KieSession kieSession() throws IOException {
        KieSession kieSession = kieContainer().newKieSession();
        KieUtils.setKieSession(kieSession);
        return kieSession;
    }

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

测试代码:

import com.kk.model.Order;
import org.junit.runner.RunWith;
import org.kie.api.KieBase;
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;
import org.springframework.test.context.junit4.SpringRunner;


@RunWith(SpringRunner.class)
@SpringBootTest(classes = Main.class)
public class Test {

    @Autowired
    private KieSession session;

    @Autowired
    private KieContainer container;

    @Autowired
    private KieBase kieBase;

    @org.junit.Test
    public void test1(){
        Order order = new Order();
        order.setPrice(50);

        session.insert(order);
        session.fireAllRules();
        session.dispose();
    }


    @org.junit.Test
    public void test2(){
        Order order = new Order();
        order.setPrice(123);

        KieSession newKieSession = container.newKieSession();
        newKieSession.insert(order);
        newKieSession.fireAllRules();
        newKieSession.dispose();
    }


    @org.junit.Test
    public void test3(){
        Order order = new Order();
        order.setPrice(234);

        KieSession kieSession = kieBase.newKieSession();
        kieSession.insert(order);
        kieSession.fireAllRules();
        kieSession.dispose();
    }
}

测试结果:

截屏20230619 232911png

截屏20230619 232927png

截屏20230619 232937png

Drools 优缺点

优点:

  1. 易于维护和管理:Drools规则引擎的规则是易于编写、修改和维护的,从而使得代码更具可读性和可维护性。
  2. 高效性能:Drools规则引擎使用了高效的编译器和运行时引擎,可以实现快速、灵活的决策处理。
  3. 灵活性:Drools规则引擎提供了丰富的语言特性和扩展性,可以根据需要进行自定义配置。
  4. 可重用性:Drools规则引擎提供了规则库和模板等功能,使得规则可以被多次重用,从而提高开发效率和代码质量。

缺点:

  1. 上手难度较大:Drools规则引擎需要理解规则引擎的基本概念和语法,并具备良好的业务分析能力。
  2. 对于简单问题过于繁琐:Drools规则引擎在处理简单问题时可能会显得比较繁琐,不如直接写代码来得简单明了。
  3. 可能会影响系统性能:Drools规则引擎在运行时需要占用一定的内存和CPU资源,可能会对系统性能产生影响。