对于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;

}

2. 容器初始化的时候去读取已有的规则数据(数据库或者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";

2. 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(他的地址)上借鉴某位大佬的实现方式所得到的启发。

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,084评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,623评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,450评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,322评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,370评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,274评论 1 300
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,126评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,980评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,414评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,599评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,773评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,470评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,080评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,713评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,852评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,865评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,689评论 2 354

推荐阅读更多精彩内容