对于Drools后期新增规则动态加载的方法

场景描述:
需要判断的规则有上千上万个或者更多。
举个例子:法律案件或者交通法规,像这类场景,每一个法条都相当于是一个条件。(如:是什么原因导致违章的?是闯了红灯啊,还是超速了呀还是超载了呀等等等)各种if-else或者switch-case判断。
这些是已有的规则判断,好说,弄个Excel表格或者插入到库中,一次性读出来,用kie提供的方式直接用就行了。但是如果后期有新增怎么办?
本文只分享新增的场景解决的方案。更新和删除部分目前还没找解决方法。
pom

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.5.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.***.drools</groupId>
    <artifactId>rule-engine</artifactId>
    <version>1.0.0</version>
    <name>rule-engine</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
        <drools.version>7.33.0.Final</drools.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>javax.inject</groupId>
            <artifactId>javax.inject</artifactId>
            <version>1</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.2</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.19</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <!--kie-->
        <dependency>
            <groupId>org.kie</groupId>
            <artifactId>kie-api</artifactId>
            <version>${drools.version}</version>
        </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-templates</artifactId>
            <version>${drools.version}</version>
        </dependency>
        <dependency>
            <groupId>org.kie</groupId>
            <artifactId>kie-internal</artifactId>
            <version>${drools.version}</version>
        </dependency>
        <dependency>
            <groupId>org.mvel</groupId>
            <artifactId>mvel2</artifactId>
            <version>2.4.4.Final</version>
        </dependency>


        <dependency>
            <groupId>com.thoughtworks.xstream</groupId>
            <artifactId>xstream</artifactId>
            <version>1.4.11.1</version>
        </dependency>

        <!-- Logging -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
        </dependency>



    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

实现思路:

  1. 使用KieHelper来build()。
    故此KieHelper要注册成一个Bean。能保持单利,保证后期动态编译的时候不会丢失已编译过的规则。
@Bean
public KieHelper kieHelper() {
    return new KieHelper();
}

DataObject-实体封装类

@Data
public class DataObject {
    private String ly;
}

RuleTemplate.java–规则封装类

public class RuleTemplate {
    private String template;

    public RuleTemplate(String template) {
        this.template = template;
    }

    public String getTemplate() {
        return template;
    }

    public void setTemplate(String template) {
        this.template = template;
    }
}

RuleTemplate的构造器里的参数其实就是Drl文件的字符串形式。根据自己的业务去拼。我这块是直接去读的库。

@Bean
public RuleTemplate ruleTemplate() {
    RuleTemplate ruleTemplate = new RuleTemplate(CreateRule.getInstance().ruleTemplate(IRulesService.queryAll()));
    return ruleTemplate;
}
  1. 容器初始化的时候去读取已有的规则数据(数据库或者Excel都行),我是直接读的库。
    2.1 查完拼接成规则字符串(拼完后可以弄到drl文件里试试规则生成的是否有问题)。
    2.2 使用kieHelper来添加已经拼好的rule字符串。然后在build。
@Bean
@PostConstruct
public KieBase kieBase() {
    //动态加载的核心实现方式
    kieHelper.addContent(ruleTemplate.getTemplate(), ResourceType.DRL);//添加规则
    KieBase kieBase = kieHelper.build();//手动编译规则
    return kieBase;
}

到这块是项目启动阶段完成的,预加载已知的规则。


以下部分是项目运行阶段来动态的去添加规则后编译并查找。

  1. 新规则字符串。
String test = "package com.pkulaw.drools.rules;\n" +
        "\n" +
        "import com.pkulaw.drools.entity.DataObject\n" +
        "\n" +
        "global java.util.List lyList;\n" +
        "\n" +
        "rule \"ly-rule-0000\"\n" +
        "agenda-group \"ly-group\"\n" +
        "dialect \"java\"\n" +
        "when\n" +
        "    d : DataObject(ly == \"测试\")\n" +
        "then\n" +
        "    lyList.add(\"这是测试\");    \n" +
        "end";
  1. KieHelper来添加和编译。
kieHelper.addContent(ruleContent, ResourceType.DRL);
//重编译
KieBase kieBase1 = kieHelper.build();
//重新注册单利
beanRegister(kieBase1);

重新注册单利的原因是每次kieHelper去build的时候会重新生成一个KieBase,而KieBase是去获取session进行fact匹配的关键。故此,要去替换Spring容器中的bean的实例。

3.1.1 替换单利bean(Deprecated)-针对Kiebase

//这个方式不够优雅,但也不是不可行,故此留着这段。
public synchronized void beanRegister(KieBase newKBase) {
    DefaultListableBeanFactory factory = (DefaultListableBeanFactory) applicationContext.getAutowireCapableBeanFactory();
    factory.removeBeanDefinition("kieBase");
    BeanDefinitionBuilder beanDefinitionBuilder =
            BeanDefinitionBuilder.genericBeanDefinition(newKBase.getClass().getName());
    // get the BeanDefinition
    BeanDefinition beanDefinition = beanDefinitionBuilder.getBeanDefinition();
    factory.registerBeanDefinition("kieBase", beanDefinition);
    factory.registerSingleton("kieBase", newKBase);
}

3.1.2 替换KieBase
上面对直接Spring容器的bean替换不够好,死脑筋没转过来。群友一提醒立马醒悟。

public class KieBaseTemplate {
    private KieBase kieBase;

    public KieBaseTemplate(KieBase kieBase) {
        this.kieBase = kieBase;
    }

    public KieBase getKieBase() {
        return kieBase;
    }

    public void update(KieBase kieBase) {
        this.kieBase = kieBase;
    }
}

原来是Kiebase注册成bean现在改成把KieBaseTemplate 注册成bean。

@Bean
@PostConstruct
public KieBaseTemplate kieBaseTemplate() {
    kieHelper.addContent(ruleTemplate.getTemplate(), ResourceType.DRL);
    KieBase kieBase = kieHelper.build();
    return new KieBaseTemplate(kieBase);
}

每次新增规则后build的时候把生成的kieBase通过update的方式重新替换掉就好了。
查找的时候通过kieBaseTemplate的getKieBase方法获取KieBase。
(有时候换个思路去思考能写出更优雅的代码来,这一段就比直接操作Bean优雅多了)
以下是查找测试代码

KieSession kSession = kieBaseTemplate.getKieBase().newKieSession();
DataObject obj1 = new DataObject();
obj1.setLy("测试");
List lyList = new ArrayList();
kSession.setGlobal("lyList", lyList);
kSession.insert(obj1);
kSession.fireAllRules();
System.out.println(lyList);

总结

这段时间由于公司需求去研究一下这个工具,基础知识阅读官方文档即可,实现方式有多重,官网介绍有InternalKnowledgeBase来实现的,还有一种是KieBuilder来实现的。KieHelper是我在GitHub(他的连接)上借鉴某位大佬的实现方式所得到的启发。

欢迎评论和批评,菜鸟一枚,有错误及时修正,以免误导他人。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

最后一根头发

努力分享自己的经验谢谢支持

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

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

打赏作者

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

抵扣说明:

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

余额充值