规则引擎之Drools

Drools概述

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

drools官网:https://drools.org

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

规则引擎构成

三部分构成:

1.Working Memory(工作内存)

2.Rule Base(规则库)

3.Inference Engine(推理引擎)

	a.Pattern Matcher(匹配器)
	
	b.Agenda(议程)
	
	c.Execution Engine(执行引擎)

术语概述:

Working Memory:

drools规则引擎会从Working Memory中获取数据并和规则文件中定义的规则进行模式匹配,在开发应用程序时调用kieSession.insert(Object)就将Object对象插入到了工作内存中。

Fact:

将一个普通的JavaBean插入到Working Memory后的对象就是Fact对象。Fact对象是应用和规则引擎进行数据交互的桥梁或通道。

Rule Base:

规则库,在规则文件中定义的规则都会被加载到规则库中

Pattern Matcher:

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

Agenda:

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

Execution Engine:

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

规则文件构成

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

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

规则体语法结构

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

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关键字,表示一个规则结束

规则属性

属性名说明
salience指定规则执行优先级
dialect指定规则使用的语言类型,取值为java和mvel
enabled指定规则是否启用
activation-group激活分组,具有相同分组名称的规则只能有一个规则触发
no-loop防止死循环

salience属性

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

rule "rules1"
    when
         eval(true)
    then
        System.out.println("1");
end

rule "rules2"
    salience 100
    when
         eval(true)
    then
         System.out.println("2");
end

enabled属性

取值为true和false,默认值为true。用于指定当前规则是否启用,如果设置的值为false则当前规则无论是否匹配成功都不会触发

rule "rules1"
    enabled false
    when
        $course:Course(score ==60)
    then
        $course.setRating("C级");
end

activation-group属性

指激活分组,取值为String类型。具有相同分组名称的规则只能有一个规则被触发。

rule "rules1"
    activation-group "myGroup"
    when
        $course:Course(score==60)
    then
        System.out.println("activation-group");
end

rule "rules2"
    activation-group "myGroup"
    salience 100
    when
        $course:Course(score==60)
    then
         System.out.println("activation-group");
end

no-loop属性

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

rule "rules1"
    no-loop true
    when
        $course:Course(score==60)
    then
        update($course);
         System.out.println("no-loop");
end

比较操作符

常规比较操作符

符号说明

| 大于
< | 小于
= | 大于等于
<= | 小于等于
== | 等于
!= | 不等于

特殊比较操作符

// 创建普通的JavaBean(Fact)对象
@Data
public class Course {

    private String name;

    private List<String> list;
}
符号说明示例
contains检查一个Fact对象的某个属性值是否包含一个指定的对象值Course(name contains “小白”)
not contains检查一个Fact对象的某个属性值是否不包含一个指定的对象值Course(name no contains “小白”)
memberOf判断一个Fact对象的某个属性是否在一个或多个集合中name memberOf list
not memberOf判断一个Fact对象的某个属性是否不在一个或多个集合中name not memberOf list
matches判断一个Fact对象的属性是否与提供的标准的Java正则表达式进行匹配name matches “小.*”
not matches判断一个Fact对象的属性是否不与提供的标准的Java正则表达式进行匹配name not matches “小.*”

Pattern模式匹配

在规则体的LHS部分定义规则并进行模式匹配。LHS部分由一个或者多个条件组成,条件又称为pattern。

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

绑定变量名可以省略,通常绑定变量名的命名一般以$开始。如果定义了绑定变量名,就可以在规则体的RHS部分使用此绑定变量名来操作相应的Fact对象。

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

//规则1:100分~90分并且为95分	为A级
rule "rules1"
    when
        $course:Course($score:score >=90 && score<=100) and
        $course2:Course(score==95)
    then
        $course.setRating("A级");
        System.out.println("$score = " + $score);
        System.out.println("course = " + $course.getScore());
        System.out.println("匹配规则1:A级");
end
$score = 95.0
course = 95.0
匹配规则1A级
course = Course(score=95.0, rating=A)

执行指定规则

满足条件的规则都会被执行,如果只想执行其中的某个规则,可以通过规则过滤器来实现执行指定规则。

//通过规则过滤器实现只执行指定规则
 kieSession.fireAllRules(new RuleNameEqualsAgendaFilter("rules1"));

Drools内置方法

规则文件的RHS部分的主要作用是通过插入,删除或修改工作内存中的Fact数据,来达到控制规则引擎执行的目的。

Drools提供了一些方法可以用来操作工作内存中的数据,操作完成后规则引擎会重新进行相关规则的匹配,原来没有匹配成功的规则在我们修改数据完成后有可能就会匹配成功

update方法

update方法的作用是更新工作内存中的数据,并让相关的规则重新匹配
rule "rules1"
    when
        $course:Course(score ==60)
    then
        $course.setRating("C级");
end


rule "rules2"
    when
        $course:Course(score <60 && score>=50)
    then
        $course.setScore(60D);
        update($course);
end

insert方法

insert方法的作用是向工作内存中插入数据,并让相关的规则重新匹配
rule "rules1"
    when
        $course:Course(score ==60)
    then
        $course.setRating("C级");
end


rule "rules2"
    when
        $course:Course(score <60 && score>=50)
    then
        Course course = new Course();
        course.setScore(60D);
        insert(course);
end

retract方法

retract方法的作用是删除工作内存中的数据,并让相关的规则重新匹配
rule "rules1"
    when
        $course:Course(score ==60)
    then
        $course.setRating("C级");
end


rule "rules2"
    when
        $course:Course(score <60 && score>=50)
    then
        Course course = new Course();
        course.setScore(60D);
        insert(course);
end

rule "rules3"
//    salience 2
    when
        $course:Course(score <60 && score>=50)
    then
        $course.setScore(60D);
         retract($course);
end

Drools的基本使用

添加依赖

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

<dependency>
    <groupId>org.drools</groupId>
    <artifactId>drools-mvel</artifactId>
    <version>7.62.0.Final</version>
</dependency>

创建kmodule.xml

创建resources/META-INF/kmodule.xml配置文件

<?xml version="1.0" encoding="UTF-8" ?>
<kmodule xmlns="http://www.drools.org/xsd/kmodule">
    <kbase name="myKbase">
        <ksession name="myksession" default="true"/>
    </kbase>
</kmodule>

创建JavaBean(Fact)对象

@Data
public class Course {
    /**
     * 分数
     */
    private Double score;
    /**
     * 评级
     */
    private String rating;
}

创建规则文件

创建resources/myrules(任意名称)目录,并创建规则文件resources/myrules/course.drl


IDEA默认集成了drools插件,在IDEA中可以识别drools文件并给与提示
在这里插入图片描述

# 逻辑包,建议包名和文件夹名一一对应。
package myrules
# 导入数据载体对象
import cn.ybzy.demo.model.Course

//规则1:100分~90分为A级
rule "rules1"
    when
        $course:Course(score >=90 && score<=100)
    then
        $course.setRating("A级");
        System.out.println("匹配规则1:A级");
end

//规则2:89分~75分为B级
rule "rules2"
    when
        $course:Course(score >=75 && score<=89)
    then
        $course.setRating("B级");
        System.out.println("匹配规则2:B级");
end

//规则3:74分~60分为C级
rule "rules3"
    when
        $course:Course(score >=60 && score<=74)
    then
        $course.setRating("C级");
        System.out.println("匹配规则3:C级");
end

//规则4:59分及其以下为D级
rule "rules4"
    when
        $course:Course(score<=59)
    then
        $course.setRating("D级");
        System.out.println("匹配规则4:D级");
end

执行测试

public static void main(String[] args) {
        KieServices kieServices = KieServices.Factory.get();
        // kieClasspathContainer容器对象; 默认自动加载META-INF/kmodule.xml
        KieContainer kieClasspathContainer = kieServices.getKieClasspathContainer();
        //从容器中获取会话对象,用于和规则引擎交互; default="true"指定则使用默认的,否则使用kmodule.xml中定义的ksession name
        KieSession kieSession = kieClasspathContainer.newKieSession();

        //创建数据载体对象,设置分数,由规则引擎根据分数规则计算不同的评级
        Course course = new Course();
        course.setScore(85D);

        //将数据提供给规则引擎,规则引擎会根据提供的数据进行规则匹配
        kieSession.insert(course);

        //激活规则引擎,如果规则匹配成功则执行规则
        kieSession.fireAllRules();

        //关闭会话
        kieSession.dispose();
        System.out.println("course = " + course);
    }
匹配规则2B级
course = Course(score=85.0, rating=B)

Spring Boot整合Drools

添加依赖

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <drools.version>7.62.0.Final</drools.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.drools</groupId>
            <artifactId>drools-compiler</artifactId>
            <version>${drools.version}</version>
        </dependency>
        <dependency>
            <groupId>org.drools</groupId>
            <artifactId>drools-mvel</artifactId>
            <version>${drools.version}</version>
        </dependency>
        <dependency>
            <groupId>org.kie</groupId>
            <artifactId>kie-spring</artifactId>
            <version>${drools.version}</version>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework</groupId>
                    <artifactId>spring-tx</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.springframework</groupId>
                    <artifactId>spring-beans</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.springframework</groupId>
                    <artifactId>spring-core</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.springframework</groupId>
                    <artifactId>spring-context</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

创建JavaBean(Fact)对象

@Data
public class Course {
    /**
     * 分数
     */
    private Double score;
    /**
     * 评级
     */
    private String rating;
}

创建规则文件

创建/resources/myrules/springbootrules.drl规则文件

package myrules

import cn.ybzy.demo.model.Course

rule "rules1"
    when
        eval(true)
    then
        System.out.println("springbootrules....");
end

rule "rules2"
    when
        $course:Course(score >=75 && score<=89)
    then
        $course.setRating("B级");
        System.out.println("匹配规则2:B级");
end

规则引擎配置类

创建配置类DroolsConfig

/**
 * 规则引擎配置类
 */
@Configuration
public class DroolsConfig {
    //指定规则文件存放的目录
    private static final String RULES_PATH = "myrules/";
    private final KieServices kieServices = KieServices.Factory.get();

    @Bean
    @ConditionalOnMissingBean
    public KieFileSystem kieFileSystem() throws IOException {
        KieFileSystem kieFileSystem = kieServices.newKieFileSystem();
        ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
        Resource[] ruleFiles = resourcePatternResolver.getResources("classpath*:" + RULES_PATH + "*.*");

        String path = null;
        for (Resource file : ruleFiles) {
            path = RULES_PATH + file.getFilename();
            kieFileSystem.write(ResourceFactory.newClassPathResource(path, "UTF-8"));
        }
        return kieFileSystem;
    }


    @Bean
    @ConditionalOnMissingBean(KieContainer.class)
    public KieContainer kieContainer() throws IOException {
        KieRepository kieRepository = kieServices.getRepository();

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

        KieBuilder kieBuilder = kieServices.newKieBuilder(kieFileSystem());
        kieBuilder.buildAll();

        return kieServices.newKieContainer(kieRepository.getDefaultReleaseId());
    }

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

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

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

创建RuleService类

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

     public void rule(){
        KieSession kieSession = kieBase.newKieSession();
        kieSession.fireAllRules();
        kieSession.dispose();
    }

    public void rule(Course course){
        KieSession kieSession = kieBase.newKieSession();
        kieSession.insert(course);
        kieSession.fireAllRules();
        kieSession.dispose();
    }
}

创建TestController

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

    @RequestMapping("/rule1")
    public String rule1() {
        ruleService.rule();
        return "success";
    }

    @RequestMapping("/rule2")
    public String rule2() {
        Course course = new Course();
        course.setScore(85D);
        ruleService.rule(course);
        return "success";
    }
}

执行测试

访问http://localhost:8080/rule

springbootrules....
匹配规则2B

动态规则

提供HTTP访问接口,将规则文件的内容存储在数据库中,当规则发生变化时调用此接口重新加载数据库中的规则。

创建数据库表存储规则

@Data
@TableName("rules")
public class Rules implements Serializable {
    private static final long serialVersionUID = 1L;
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    private String ruleName;
    private String content;
}

数据库表添加规则

package dynamicRules

rule "rules1"
    when
        eval(true)
    then
        System.out.println("动态规则....");
end

创建Mapper接口

@Mapper
public interface RulesMapper extends BaseMapper<Rules> {
}

加载数据库规则

@Service
public class ReloadDroolsRulesService {

    public static KieContainer kieContainer;

    @Autowired
    private RulesMapper rulesMapper;

    public KieContainer loadKieContainer() {
        List<Rules> rules = rulesMapper.selectList(null);

        KieServices kieServices = KieServices.Factory.get();
        KieRepository kieRepository = kieServices.getRepository();
        KieFileSystem kieFileSystem = kieServices.newKieFileSystem();

        for (Rules rule : rules) {
            String drl = rule.getContent();
            kieFileSystem.write("src/main/resources/dynamicRules/" + rule.getRuleName() + ".drl", drl);
        }

        KieBuilder kb = kieServices.newKieBuilder(kieFileSystem);
        kb.buildAll();
        kieContainer = kieServices.newKieContainer(kieRepository.getDefaultReleaseId());
        return kieContainer;
    }
}

项目启动时自动加载规则

@Component
@Slf4j
public class CommandLineRunnerImpl implements CommandLineRunner {

    @Resource
    private ReloadDroolsRulesService reloadDroolsRulesService;

    @Override
    public void run(String... args) {
        log.info(" load rules ...");
        reloadDroolsRulesService.loadKieContainer();
    }
}

提供HTTP访问接口

@RestController
public class RulesReloadController {

    @Autowired
    private ReloadDroolsRulesService reloadDroolsRulesService;

    @RequestMapping("/reload")
    public String reload() {
        reloadDroolsRulesService.loadKieContainer();
        return "success";
    }

    @RequestMapping("/test")
    public String test() {
        KieSession kieSession = ReloadDroolsRulesService.kieContainer.newKieSession();
        kieSession.fireAllRules();
        kieSession.dispose();
        return "success";
    }
}

动态规则测试

1.访问/test接口后修改数据库规则内容

2.访问/reload接口后再访问/test接口

动态规则....
INFO 7152 --- [io-8080-exec-10] o.d.c.kie.builder.impl.KieContainerImpl  : Start creation of KieBase: defaultKieBase
INFO 7152 --- [io-8080-exec-10] o.d.c.kie.builder.impl.KieContainerImpl  : End creation of KieBase: defaultKieBase
动态修改规则....
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

CodeDevMaster

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

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

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

打赏作者

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

抵扣说明:

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

余额充值