SpringBoot-Drools

x33g5p2x  于2021-09-25 转载在 Spring  
字(31.2k)|赞(0)|评价(0)|浏览(350)

SpringBoot-Drools

什么是规则引擎

Drools实现了将业务规则从应用程序代码中分离出来。规则引擎使用特定的语法编写业务规则,规则引擎可以接受数据输入、解释业务规则、并根据业务规则做出相应的决策

规则引擎的能干什么

假设我们碰到一个需求:我们要给公司工作满5年的员工发纪念章

一般我们做法是:

if(员工工作年限>=5){
      给员工发奖();
 }

这种做法的优点是简单快捷高效,但是假如我们已经开发好了程序,正在线上运行,突然,公司来了需求变更,不行,纪念章不够,我们要改变规则,工作满10年,而且是管理岗位,才发纪念章,

在比如:会员积分系统(今天要新注册会员送10积分,明天要改成注册送优惠券,后天搞活动要改成注册自动变成高级会员…),此类需求,一般都是通过写if分支来实现的,参考下面:

if (规则条件1){
   //处理1
}
else if (规则条件2){
   //处理2
}
else if (规则条件3){
   //处理3
}
...

这时候,苦逼的程序员只能火速的改代码,然后 打包,然后上线,万一 万一 手忙脚乱发生各种意外,呵呵,完蛋.

这种代码毫无营养,而且很枯燥,有没有办法,将业务规则从代码中抽离出来,以后规则变了,不用改代码,只改规则配置就行?

今天要介绍的Drools,可以很好的解决此类问题,Drools是一个业务规则管理的开源框架,现在归到jboss旗下,本文将介绍一些基本的用法,方便大家快速上手。

那规则引擎如何处理呢?

首先,发奖程序,先去访问,规则服务-》规则服务再去访问对应的规则=》最后规则条件通过了之后, 再去请求发奖程序。这样假如公司要变更发奖规则,那只需要在规则系统上对对应规则进行变更,重新发布规则就可以满足需求。当然,从系统架构层面上来说,系统的架构就变得复杂了,所以在实际运用中,依然需要根据实际情况来决定是否采用规则系统。

注意:,Drools可以入参但是没有返回值,这也就表示了,Drools就是为了完成某件事件而存在的

规则引擎的优点

实现业务逻辑与业务规则的分离,业务规则的集中管理
可以动态修改规则,动态发布,快速响应需求变更
可以减低开发成本,可以让业务人员参与到规则编写,降低软件工程师的介入程度(当然即便是drools 现在也很难做到,毕竟对业务人员的要求比较高,但是可以降低介入程度)
可以降低业务复杂度,降低开发成本,当然,与之相对的,系统的架构变得更加复杂。

规则引擎的缺点

使程序的架构变得复杂,对程序架构提出了更高的要求
要学习规则脚本的语法
某些特殊的场景或者要求,导致各种莫名的坑,解决办法就是换一种思维方式

Drools规则文件

在 Drools 当中,一个标准的规则文件就是一个以.drl结尾的文本文件,由于它是一个标准的文本文件,所以可以通过一些记事本工具对其进行打开、查看和编辑。

规则是放在规则文件当中的,一个规则文件可以存放多个规则,除此之外,在规则文件当中还可以存放用户自定义的函数、数据对象及自定义查询等相关在规则当中可能会用到的一些对象。

常用的有:package package-name、imports、globals、functions、queries、rules

对于一个规则文件而言,首先声明package 是必须的,除package 之外,其它对象在规则文件中的顺序是任意的,也就是说在规则文件当中必须要有一个package 声明,同时package 声明必须要放在规则文件的第一行。在Drools 的规则文件当中package 对于规则文件中规则的管理只限于逻辑上的管理,,而不管其在物理上的位置如何,这点是规则与Java 文件的package 的区别。对于同一package 下的用户自定义函数、自定义的查询等,不管这些函数与查询是否在同一个规则文件里面,在规则里面是可以直接使用的,这点和Java 的同一package 里的Java类调用是一样的。

Drools规则语法

注释

在drl形式的规则文件中使用注释和Java类中使用注释一致,分为单行注释和多行注释。

单行注释用"//“进行标记,多行注释以”//“开始,以”//"结束。如下示例:

//规则rule1的注释,这是一个单行注释

/*
规则rule2的注释,
这是一个多行注释
*/

基本结构

rule "name"
attributes
    when
        LHS
    Then
        RHS
end

一个规则通常包括三个部分:属性部分(attribute)、条件部分(LHS)和结果部分(RHS)。对于一个完整的规则来说,这三个部分都是可选的,也就是说如下 所示的规则是合法的:

rule "name"
when
then
end
条件部分

条件部分又被称之为Left Hand Side,简称为LHS,下文当中,如果没有特别指出,那么所说的LHS 均指规则的条件部分,在一个规则当中when 与then 中间的部分就是LHS 部分。在LHS 当中,可以包含0~n 个条件,如果LHS 部分没写的话,那么引擎会自动添加一个eval(true)的条件,由于该条件总是返回true,所以LHS 为空的规则总是返回true。LHS 部分是由一个或多个条件组成,条件又称之为pattern(匹配模式),多个pattern之间用可以使用and 或or 来进行连接,同时还可以使用小括号来确定pattern 的优先级。
一个pattern 的语法如下:

绑定变量名: Object ( field 约束 )

对于一个pattern 来说“绑定变量名”是可选的,如果在当前规则的LHS 部分的其它的pattern 要用到这个对象,那么可以通过为该对象设定一个绑定变量名来实现对其引用,对于绑定变量的命名,通常的作法是为其添加一个“$”符号作为前缀,这样可以很好的与Fact的属性区别开来;绑定变量不仅可以用在对象上,也可以用在对象的属性上面,命名方法与对象的命名方法相同;“field 约束”是指当前对象里相关字段的条件限制,示例如下:

rule "rule1"
when
$customer:Customer(age>20,gender==’male’) 
Order(customer==$customer,price>1000)
then
<action>…
end

此段规则的含义为:

第一个pattern 有三个约束(带绑定变量),分别是:

  1. 对象类型必须是Cutomer;
  2. 同时Cutomer 的age 要大于20
  3. 且gender 要是male;

第二个pattern (不带绑定变量)也有三个约束,分别是:

对象类型必须是Order,
1.
同时Order 对应的Cutomer 必须是前面的那个Customer
1.
且当前这个Order 的price 要大于1000。

在这两个pattern 没有符号连接,在Drools当中在pattern 中没有连接符号,那么就用and 来作为默认连接,所以在该规则的LHS 部分中两个pattern 只有都满足了才会返回true。默认情况下,每行可以用“;”来作为结束符(和Java 的结束一样),当然行尾也可以不加“;”结尾。

约束连接

对于对象内部的多个约束的连接,可以采用“&&”(and)、“||”(or)和“,”(and)来实现,“&&”(and)、“||”(or)和“,”这三个连接符号如果没有用小括号来显示的定义优先级的话,那么它们的执行顺序是:“&&”(and)、“||”(or)和“,” “&&”优先级最高,表面上看“,”与“&&”具有相同的含义,但是有一点需要注意,“,”与“&&”和“||”不能混合使用,

也就是说在有“&&”或“||”出现的LHS 当中,是不可以有“,”(逗号) 连接符出现的,反之亦然。

比较操作符

Drools当中共提供了十二种类型的比较操作符,分别是:>、>=、<、<=、= =、!=、contains、not contains、memberof、not memberof、matches、not matches;

在这十二种类型的比较操作符当中,前六个是比较常见也是用的比较多的比较操作符,着重对后六种类型的比较操作符进行介绍。

contains

比较操作符contains 是用来检查一个对象的某个字段是否包含一个指定的对象。 说实话没啥用(就是类型的判断)

when
$order:Order();
$customer:Customer(age >20, orders contains $order);
then
System.out.println($customer.getName());
end

contains 只能用于对象的某个Collection/Array 类型的字段与另外一个值进行比较,作为比较的值可以是一个静态的值,也可以是一个变量(绑定变量或者是一个global 对象)。

not contains

not contains 作用与contains 作用相反,not contains 是用来判断一个对象的某个字段(Collection/Array 类型)是不是不包含一个指定的对象,和contains 比较符相同,它也只能用在对象的field 当中。 说实话没啥用(就是类型的判断)

memberOf

memberOf 是用来判断某个对象的某个字段是否在一个集合(Collection/Array)当中,用法与contains 有些类似,但也有不同,memberOf 的语法如下:

Object(fieldName memberOf value[Collection/Array])

注意: 集合是在右边的,如果集合类型里面的值是对象那么比较的是类型而不是对象(实现equals和hashCode也不行)String和包装类型除外

列:

rule "rule1"
when
$str:String();
$customer:Customer(age >20, $str memberOf list);
then
System.out.println("hello,word!"+$customer.getName());
end

可以看到memberOf 中集合类型的数据是作为被比较项的,集合类型的数据对象位于memberOf 操作符后面,同时在用memberOf 比较操作符时被比较项一定要是一个变量(绑定变量或者是一个global 对象),而不能是一个静态值。

not memberOf

该操作符与memberOf 作用洽洽相反,是用来判断对象当中某个字段值是不是中某个集合(Collection/Array)当中,同时被比较的集合对象只能是一个变量(绑定变量或global对象)。

matches

matches 是用来对某个Fact 的字段与标准的Java 正则表达式进行相似匹配,被比较的字符串可以是一个标准的Java 正则表达式,但有一点需要注意,那就是正则表达式字符串当中不用考虑“\”的转义问题

rule "rule1"
when
$customer:Customer(name matches "李.*");
then
System.out.println($customer.getName());
end
not matches

与matches 作用相反,是用来将某个的字段与一个Java 标准正则表达式进行匹配,看是不是能与正则表达式匹配。

结果部分

结果部分又被称之为Right Hand Side,简称为RHS,在一个规则当中then 后面部分就是RHS,只有在LHS 的所有条件都满足时RHS 部分才会执行。

RHS 部分是规则真正要做事情的部分,可以将因条件满足而要触发的动作写在该部分当中,在RHS 当中可以使用LHS 部分当中定义的绑定变量名、设置的全局变量、或者是直接编写Java 代码(对于要用到的Java 类,需要在规则文件当中用import 将类导入后方能使用,这点和Java 文件的编写规则相同)。

在规则当中LHS 就是用来放置条件的,所以在RHS 当中虽然可以直接编写Java 代码,但不建议在代码当中有条件判断,如果需要条件判断,那么请重新考虑将其放在LHS 当中,否则就违背了使用规则的初衷。

在 Drools 当中,在RHS 里面,提供了一些对当前内存实现快速操作的宏函数或宏对象,比如insert/insertLogical、update 和retract 就可以实现对当前指定的对象进行新增、删除或者是修改. 那么我们外面的java对象内容也会被改变了

规则文件的RHS部分的主要作用是通过插入,删除或修改工作内存中的对象数据,来达到控制规则引擎执行的目的。Drools提供了一些方法可以用来操作工作内存中的数据,操作完成后规则引擎会重新进行相关规则的匹配原来没有匹配成功的规则在我们修改数据完成后有可能就会匹配成功了。 所以在某些情况下因考虑不周调用insert、update 或retract容,将之前给改变了->易发生死循环,这点大家需要注意.

insert

语法: insert(new Object());

也就是原来没有匹配成功的规则在我们修改数据完成后有可能就会匹配成功了。

列:

rule "rule1"
when
$c: Customer(age>20);
then
$c.setName("张三1");
insert($c);
end
update

update 函数意义与其名称一样,用来实现对当前内存当中的对象进行更新,update 宏函数的作用与StatefulSession 对象的update 方法的作用基本相同,都是用来告诉当前的Working Memory 该Fact 对象已经发生了变化。

rule "rule1"
when
$customer:Customer(name=="张三",age<10);
then
$customer.setAge($customer.getAge()+1);
update($customer);
System.out.println("----------"+$customer.getName());
end
retract

retract 用来将内存当中某个对象从内存当中删除,下面就通过一个例子来说明retract 宏函数的用法。

这个删除不影响外部java对象,而是在所有调用这个对象规则都没了 (不建议使用,可能会发生很多未知的问题)

rule "rule1"
when
$customer:Customer(name=="张三",age<10);
then
retract($customer);
System.out.println("----------"+$customer.getName());
end

运行上面这个代码发现内容没有被改变,因为删除只会作用在规则的判断上,也就是LHS 中

modify

modify 是一个表达式块,它可以快速实现对,对象多个属性进行修改,修改完成后会自动更新到当前的内存当中。它的基本语法格式如下:

modify(fact-expression){ <修改Fact 属性的表达式>[,<修改Fact 属性的表达式>/*] }

rule "rule1"
when
$customer:Customer(name=="张三",age==20);
then
System.out.println("modify before customerid:"+$customer.getId()+";age:"+$customer.getAge());
modify($customer){
setId(10),
setAge(30)
}
System.out.println("modify after customerid:"+$customer.getId()+";age:"+$customer.getAge());
end

这里有一点需要注意,那就是和insert、update、retract 对Working Memory 的操作一样,一旦使用了modify 块对某个对象的属性进行了修改,那么会导致引擎重新检查所有规则是否匹配条件,而不管其之前是否执行过。

属性部分

规则属性是用来控制规则执行的重要工具,在规则的属性共有13 个,它们分别是:activation-group、agenda-group、
auto-focus、date-effective、date-expires、dialect、duration、enabled、lock-on-active、no-loop、ruleflow-group、salience、when,这些属性分别适用于不同的场景,下面我们就来分别介绍这些属性的含义及用法。

salience

用来设置规则执行的优先级,salience 属性的值是一个数字,数字越大执行优先级越高,同时它的值可以是一个负数。默认情况下,规则的ssalience 默认值为0,所以如果我们不手动设置规则的salience 属性,那么它的执行顺序是随机(但是一般都是按照加载顺序。)

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

no-loop

no-loop 属性的作用是用来控制已经执行过的规则在条件再次满足时是否再次执行。默认情况下规则的no-loop属性的值为false,如果no-loop 属性值为true,那么就表示该规则只会被引擎检查一次,

no-loop 是对于自己规则的操作引起重新匹配,只执行一次。但是不会限制其他规则的操作引起的重新匹配。所以如果有两个
规则都是一直满足条件,且then 中有update 操作。那么将会进入死循环,此时应该使用 lock-on-active 属性保证只匹配一次。

rule "rule1"
salience 1
no-loop true
when
eval(true)
then
System.out.println("rule1");
end

date-effective

控制规则只有在到达后才会触发。只有当系统时间>=date-effective 设置的时间值时,规则才会触发执行,否则执行将不执行。在没有设置该属性的情况下,规则随时可以触发,没有这种限制。

date-effective 可接受的日期格式为“dd-MMM-yyyy”,例如2009 年9 月25 日
如果您的操作系统为中文的,那么应该写成“25-Sep-2009”;如果是英文操作系统“25-九月-2009”

但是这种方式比较麻烦,我们可以在调用规则前进行时间格式的修改,使用通用的时间格式

System.setProperty("drools.dateformat", "yyyy-MM-dd HH:mm");

然后在规则文件中

rule "rule1"
date-effective "2021-8-1 12:54"
when
eval(true);
then
System.out.println("rule1 is execution!");
end

date-expires

该属性的作用与date-effective 属性恰恰相反, date-expires 的作用是用来设置规则的有效期。如果date-expires 的值大于系统时间,那么规则就执行,否则就不执行。具体用法与date-effective 属性相同。

enabled

enabled 属性比较简单,它是用来定义一个规则是否可用的。该属性的值是一个布尔值,默认该属性的值为true,表示规则是可用的,如果手工为一个规则添加一个enabled 属性,并且设置其enabled 属性值为false,那么引擎就不会执行该规则。

rule "rule1"
enabled false
when
eval(true);
then
System.out.println("rule1 is enabled!");
end

dialect

没什么卵用

该属性用来定义规则当中要使用的语言类型,目前Drools 版本当中支持两种类型的语言:mvel 和java,默认情况下,如果没有手工设置规则的dialect,那么使用的java 语言。

duration

已废弃。设置该属性,规则将指定的时间之后在另外一个线程里触发。属性值为一个长整型,单位是毫秒。如果属性值设置为0,则标示立即执行,与未设置相同。

lock-on-active

确认规则只执行一次。 将lock-on-action 属性的值设置为true,可能避免因某些对象被修改而使已经执行过的规则再次被激活执行。

lock-on-active 是no-loop 的增强版属性。lock-on-active 属性默认值为false。

rule "rule1"
lock-on-active true
when
eval(true)
then
System.out.println("rule lock-on-active");
end

activation-group

该属性的作用是将若干个规则划分成一个组,用一个字符串来给这个组命名,这样在执行的时候,具有相同activation-group 属性的规则中只要有一个会被执行,其它的规则都将不再执行。

在一组具有相同activation-group 属性的规则当中,只有一个规则会被执行,其它规则都将不会被执行。当然对于具有相同activation-group 属性的规则当中究竟哪一个会先执行,则可以用类似salience 之类属性来实现。

rule "rule1"
activation-group "test"
when
eval(true)
then
System.out.println("rule1 activation-group1");
end

rule "rule 2"
activation-group "test"
when
eval(true)
then
System.out.println("rule1 activation-group2");
end

rule1 和rule2 这两个规则因为具体相同名称的activation-group 属性,所以它们只有一个会被执行。

agenda-group

对activation-group进行升级,不是只执行分组内的一个了,而是执行指定分组内的全部规则了

agenda-group 属性的值也是一个字符串,通过这个字符串,可以将规则分为若干个Agenda Group,默认情况下,/*/*引擎在调用这些设置了agenda-group 属性的规则的时候需要显示的指定某个Agenda Group 得到Focus(焦点),这样位于该Agenda Group 当中的规则才会触发执行,否则将不执行。

rule "rule1"
agenda-group "001"
when
eval(true)
then
System.out.println("rule1 agenda-group");
end

rule "rule 2"
agenda-group "002"
when
eval(true)
then
System.out.println("rule2 agenda-group");
end

rule "rule 3"
agenda-group "002"
when
eval(true)
then
System.out.println("rule3 agenda-group");
end
kieSession.getAgenda().getAgendaGroup("002").setFocus(); //设置焦点-执行的规则
        kieSession.fireAllRules();

auto-focus

前面我们也提到auto-focus 属性,它的作用是用来在已设置了agenda-group 的规则上设置该规则是否可以自动独取Focus,如果该属性设置为true,那么在引擎执行时,就不需要显示的为某个Agenda Group 设置Focus,否则需要。

对于规则的执行的控制,还可以使用Agenda Filter 来实现。在Drools 当中,提供了一个名为org.drools.runtime.rule.AgendaFilter 的Agenda Filter 接口,用户可以实现该接口,通过规则当中的某些属性来控制规则要不要执行。org.drools.runtime.rule.AgendaFilter 接口只有一个方法需要实现,方法体如下:

public boolean accept(Activation activation);

在该方法当中提供了一个Activation 参数,通过该参数我们可以得到当前正在执行的规则对象或其它一些属性,该方法要返回一个布尔值,该布尔值就决定了要不要执行当前这个规则,返回true 就执行规则,否则就不执行。

在引擎执行规则的时候,我们希望使用规则名来对要执行的规则做一个过滤,此时就可以通过AgendaFilter 来实现,示例代码既为我们实现的一个AgendaFilter 类源码。

package com.drools.filter;

import org.kie.api.runtime.rule.AgendaFilter;
import org.kie.api.runtime.rule.Match;

public class TestAgendaFilter implements AgendaFilter {
    private String startName;
    public TestAgendaFilter(String startName){
        this.startName=startName;
    }
    @Override
    public boolean accept(Match match) {
        String ruleName=match.getRule().getName();
        //字符串开头是否包含
        if(ruleName.startsWith(this.startName)){
            return true;
        }else{
            return false;
        }
    }
}

规则:

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

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

java 中调用

TestAgendaFilter filter= new TestAgendaFilter("activa");
int count = ks.fireAllRules(filter);

最后只有前缀是activa被调用了

ruleflow-group

在使用规则流的时候要用到ruleflow-group 属性,该属性的值为一个字符串,作用是用来将规则划分为一个个的组,然后在规则流当中通过使用ruleflow-group 属性的值,从而使用对应的规则。

注意: 一旦使用了ruleflow-group那么这个规则就不会自动执行,需要使用getAgendaGroup来进行激活

rule "rules1"
ruleflow-group "group1"
when
eval(true)
then
System.out.println("ruleflow-group1");
end

rule "rules2"
ruleflow-group "group1"
when
eval(true)
then
System.out.println("ruleflow-group2");
end

rule "rules3"
ruleflow-group "group2"
when
eval(true)
then
System.out.println("ruleflow-group3");
end

java 调用

kieSession.getAgenda().getAgendaGroup("group1").setFocus();
        kieSession.fireAllRules();

函数

函数是定义在规则文件当中一代码块,作用是将在规则文件当中若干个规则都会用到的业务操作封装起来,实现业务代码的复用,减少规则编写的工作量。函数的编写位置可以是规则文件当中package 声明后的任何地方.

Drools 当中函数声明语法:

function void/Object functionName(Type arg...) {
/*函数体的业务代码*/
}

Drools 当中的函数以function 标记开头,如果函数体没有返回值,那么function 后面就是void,如果有返回值这里的void 要换成对应的返回值对象,接下来就是函数的名称函数名称的定义可以参考Java 类当中方法的命名原则,对于一个函数可以有若干个输入参数,所以函数名后面的括号当中可以定义若干个输入参数。

列:

在then中使用函数

function String hello(String name) {
    return "Hello "+name+"!";
}

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

在when中使用函数,注意不能直接使用

function Boolean namePd(String name) {
    if ("hu".equals(name)){
        return true;
    }
    return  false;
}

rule "rules1"
    when
        eval(namePd("hu"));
    then
       System.out.println("rules1");
end

在或者参与判断中

rule "rules1"
    when
        $u:User(namePd(name)&& age>20);
    then
       System.out.println("rules1");
end

Drools 为我们提供了一个特殊的import 语句:通过该import语句,可以实现将一个Java 类中公共静态方法引入到一个规则文件当中,使得该文件当中的规则可以像使用普通的Drools 函数一样来使用Java 类中某个静态方法。

import com.RuleTools;  //将RuleTools类的全部静态方法引入

调用方式

RuleTools.printInfo(...)

我们创建一个java类,写一些静态方法

package com.drools.drfunction;

public class TestFunction {

    public  static  void  test1(String name){
        System.out.println(name);
    }
    public  static  String  test2(String name){
      return   name;
    }

}

规则文件

package rules;
import com.drools.drfunction.TestFunction;

rule "rules1"
    when
        eval(true);
    then
       TestFunction.test1("hu");
       System.out.println(TestFunction.test2("hu"));
end

SpringBoot-Drools文件版

需要的Maven

<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.4.RELEASE</version>
    </parent>
    <dependencies>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>2.4.1</version>
        </dependency>

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

        <dependency>
            <groupId>org.kie</groupId>
            <artifactId>kie-spring</artifactId>
            <version>7.14.0.Final</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <version>2.1.1.RELEASE</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>

Drools 配置文件

package com.drools.config;

import org.kie.api.KieBase;
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.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 DroolsAutoConfiguration {
    private static final String RULES_PATH = "rules/";

    @Bean
    @ConditionalOnMissingBean(KieFileSystem.class)
    public KieFileSystem kieFileSystem() throws IOException {
        KieFileSystem kieFileSystem = getKieServices().newKieFileSystem();
        for (Resource file : getRuleFiles()) {
            kieFileSystem.write(ResourceFactory.newClassPathResource(RULES_PATH + file.getFilename(), "UTF-8"));
        }
        return kieFileSystem;
    }

    private Resource[] getRuleFiles() throws IOException {
        ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
        return resourcePatternResolver.getResources("classpath*:" + RULES_PATH + "**/*.*");
    }

    @Bean
    @ConditionalOnMissingBean(KieContainer.class)
    public KieContainer kieContainer() throws IOException {
        final KieRepository kieRepository = getKieServices().getRepository();
        kieRepository.addKieModule(new KieModule() {
            @Override
            public ReleaseId getReleaseId() {
                return kieRepository.getDefaultReleaseId();
            }
        });
        KieBuilder kieBuilder = getKieServices().newKieBuilder(kieFileSystem());
        kieBuilder.buildAll();
        return getKieServices().newKieContainer(kieRepository.getDefaultReleaseId());
    }



    @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();
        return kieSession;
    }

    @Bean
    @ConditionalOnMissingBean(KModuleBeanFactoryPostProcessor.class)
    public KModuleBeanFactoryPostProcessor kiePostProcessor() {
        return new KModuleBeanFactoryPostProcessor();
    }
    public KieServices getKieServices() {
        System.setProperty("drools.dateformat","yyyy-MM-dd");
        return KieServices.Factory.get();
    }
}

规则文件

resources-> rules -> HelloWord.drl

package rules;
import com.drools.entity.Order;
import com.drools.entity.Customer;

rule "rules1"
    when
        eval(true);
    then
       System.out.println("Hello,World");
end

rule "rules2"
when
$order:Order();
then
System.out.println($order.getPrice());
end

测试文件

import com.drools.BlogAdminApplication;
import com.drools.entity.Order;
import org.drools.core.base.RuleNameEndsWithAgendaFilter;
import org.drools.core.base.RuleNameEqualsAgendaFilter;
import org.junit.Test;
import org.junit.runner.RunWith;
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 = BlogAdminApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class DroolsJunitTest {

    @Autowired
    KieSession kieSession;

    //执行全部文件的全部,可执行的规则
    @Test
    public void test1() {

        kieSession.fireAllRules();
    }

    //执行指定名称的规则
    @Test
    public void test2() {

        kieSession.fireAllRules(new RuleNameEqualsAgendaFilter("rules1"));
    }
    @Test
    public void test3() {
        Order order=new Order();
        order.setPrice(100);
        kieSession.insert(order); //多个参数那么就使用多次insert
        kieSession.fireAllRules();
    }

}

SpringBoot-Drools-数据库动态加载规则

下面写了一整套方案直接搬过去用就行,必须先把SpringBoot-Drools文件版玩明白了 ,因为我们编写的时候会先写文件版,测试没问题了在放入数据库中

需要的Maven

<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.4.RELEASE</version>
    </parent>
    <dependencies>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>2.4.1</version>
        </dependency>

        <!--数据库驱动 告诉Springboot 我们使用mysql数据库-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>

        <!--jdbc的启动器,默认使用HikariCP连接池-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>

        <!--Mybati 和Spring boot 自动整合依赖 -->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.3</version>
        </dependency>

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

        <dependency>
            <groupId>org.kie</groupId>
            <artifactId>kie-spring</artifactId>
            <version>7.14.0.Final</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <version>2.1.1.RELEASE</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>

    <!-- 自动查找主类 用于打包 -->
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

Drools 配置文件

package com.drools.config;

import org.kie.api.KieBase;
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.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 DroolsAutoConfiguration {
    private static final String RULES_PATH = "rules/";

    @Bean
    @ConditionalOnMissingBean(KieFileSystem.class)
    public KieFileSystem kieFileSystem() throws IOException {
        KieFileSystem kieFileSystem = getKieServices().newKieFileSystem();
        for (Resource file : getRuleFiles()) {
            kieFileSystem.write(ResourceFactory.newClassPathResource(RULES_PATH + file.getFilename(), "UTF-8"));
        }
        return kieFileSystem;
    }

    private Resource[] getRuleFiles() throws IOException {
        ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
        return resourcePatternResolver.getResources("classpath*:" + RULES_PATH + "**/*.*");
    }

    @Bean
    @ConditionalOnMissingBean(KieContainer.class)
    public KieContainer kieContainer() throws IOException {
        final KieRepository kieRepository = getKieServices().getRepository();
        kieRepository.addKieModule(new KieModule() {
            @Override
            public ReleaseId getReleaseId() {
                return kieRepository.getDefaultReleaseId();
            }
        });
        KieBuilder kieBuilder = getKieServices().newKieBuilder(kieFileSystem());
        kieBuilder.buildAll();
        return getKieServices().newKieContainer(kieRepository.getDefaultReleaseId());
    }



    @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();
        return kieSession;
    }

    @Bean
    @ConditionalOnMissingBean(KModuleBeanFactoryPostProcessor.class)
    public KModuleBeanFactoryPostProcessor kiePostProcessor() {
        return new KModuleBeanFactoryPostProcessor();
    }
    public KieServices getKieServices() {
        System.setProperty("drools.dateformat","yyyy-MM-dd");
        return KieServices.Factory.get();
    }
}

数据库表

CREATE TABLE `t_drools_rule` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `business_id` varchar(64) DEFAULT NULL COMMENT '业务主键',
  `rule_content` longtext COMMENT '规则',
  `group` varchar(255) DEFAULT NULL COMMENT '规则分组',
  `describe` text COMMENT '规则描述',
  PRIMARY KEY (`id`) USING BTREE,
  KEY `business_id` (`business_id`),
  KEY `business_id_2` (`business_id`,`group`)
) ENGINE=InnoDB AUTO_INCREMENT=22 DEFAULT CHARSET=utf8;

测试数据

INSERT INTO  `t_drools_rule` (`id`, `business_id`, `rule_content`, `group`, `describe`) VALUES (1, '0001', 'package rules;\r\n\r\nrule \"rules1\"\r\n when\r\n eval(true);\r\n then\r\n System.out.println(\"hu\");\r\nend\r\n\r\n', 'a', '测试');
INSERT INTO  `t_drools_rule` (`id`, `business_id`, `rule_content`, `group`, `describe`) VALUES (2, '0001', 'rule \"rules2\"\r\n when\r\n eval(true);\r\n then\r\n System.out.println(\"hu1\");\r\nend', 'a', '测试');
INSERT INTO  `t_drools_rule` (`id`, `business_id`, `rule_content`, `group`, `describe`) VALUES (3, '0002', 'package rules;\r\n\r\nrule \"rules3\"\r\n when\r\n eval(true);\r\n then\r\n System.out.println(\"hu3\");\r\nend\r\n\r\n\r\n', 'b', '测试');

application.yml

server:
  port: 10121

spring:
  # 开发阶段关闭thymeleaf的模板缓存 否则每次修改都需要重启引导
  thymeleaf:
    cache: false
  datasource:
    driverClassName: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/voidme?useUnicode=true&characterEncoding=utf8&autoReconnect=true&failOverReadOnly=false&serverTimezone=UTC
    username: root
    password: root
    hikari.idle-timeout: 60000
    hikari.maximum-pool-size: 30
    hikari.minimum-idle: 10


#spring集成Mybatis环境
#实体类别名扫描包 如果使用接口的话这个可以省略
# 加载Mybatis映射文件 classpath:mapper/*Mapper.xml 代表是 resources 下mapper包里所有是 ..Mapper.xml结尾的xml配置文件 如果使用接口的话这个可以省略
mybatis:
  type-aliases-package: com.drools.entity
  mapper-locations: classpath:mybaitis/*Mapper.xml
  configuration:
    map-underscore-to-camel-case: true

BlogAdminApplication

package com.drools;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class BlogAdminApplication {
    public static void main(String[] args) {
        SpringApplication.run(BlogAdminApplication.class,args);
    }
}

DroolsRuleMapper.xml

<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--命名空间 绑定 接口类-->
<mapper namespace="com.drools.dao.DroolsRuleDao">
    
    <select id="getDroolsRuleById" resultType="DroolsRule">
         SELECT   * FROM   t_drools_rule WHERE id=#{id}
    </select>

    <select id="getDroolsRuleBybusinessIdAndGroup" resultType="DroolsRule">
        SELECT * FROM  t_drools_rule where  business_id=#{businessId} AND `group`=#{group}
    </select>

</mapper>

DroolsRuleDao

package com.drools.dao;

import com.drools.entity.DroolsRule;
import org.apache.ibatis.annotations.Mapper;

import java.util.List;

@Mapper
public interface DroolsRuleDao {
    DroolsRule getDroolsRule(int id);

    List<DroolsRule> getDroolsRuleBybusinessIdAndGroup(String businessId ,String group);
}

DroolsRuleService

package com.drools.service;

import org.kie.api.runtime.KieSession;

public interface DroolsRuleService {

    KieSession getDroolsRuleById(int id);

    KieSession getDroolsRuleBybusinessIdAndGroup(String businessId , String group);
}

DroolsRuleServiceImpl

package com.drools.service.impl;

import com.drools.dao.DroolsRuleDao;
import com.drools.entity.DroolsRule;
import com.drools.service.DroolsRuleService;
import org.drools.core.base.RuleNameEndsWithAgendaFilter;
import org.drools.core.base.RuleNameEqualsAgendaFilter;
import org.drools.core.base.RuleNameMatchesAgendaFilter;
import org.drools.core.base.RuleNameStartsWithAgendaFilter;
import org.drools.core.impl.InternalKnowledgeBase;
import org.drools.core.impl.KnowledgeBaseFactory;
import org.kie.api.io.ResourceType;
import org.kie.api.runtime.KieSession;
import org.kie.internal.builder.KnowledgeBuilder;
import org.kie.internal.builder.KnowledgeBuilderFactory;
import org.kie.internal.io.ResourceFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.UnsupportedEncodingException;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

@Service
public class DroolsRuleServiceImpl  implements DroolsRuleService {
    //缓存 -> 注意:如果数据库规则的数量很多的话,是会照成内存溢出 解决办法可以加限制 比如如果map中数量达到200了那么就清空缓存,从新加载->
    public  static  ConcurrentHashMap<String,InternalKnowledgeBase> concurrentHashMap=new ConcurrentHashMap<>();

    @Autowired
    private DroolsRuleDao droolsRuleDao;

    @Override
    public KieSession getDroolsRuleById(int id) {
        if (concurrentHashMap.size()>200) { //防止内存溢出
            concurrentHashMap.clear();//清空缓存
        }
        InternalKnowledgeBase internalKnowledgeBase = concurrentHashMap.get(String.valueOf(id));
        if (internalKnowledgeBase==null) {

            try {
                KnowledgeBuilder kb = KnowledgeBuilderFactory.newKnowledgeBuilder();
                //装入规则,可以装入多个
                DroolsRule droolsRule = droolsRuleDao.getDroolsRuleById(id);
                kb.add(ResourceFactory.newByteArrayResource(droolsRule.getRuleContent().getBytes("utf-8")), ResourceType.DRL);
                internalKnowledgeBase = KnowledgeBaseFactory.newKnowledgeBase();
                internalKnowledgeBase.addPackages(kb.getKnowledgePackages());
                concurrentHashMap.put(String.valueOf(id),internalKnowledgeBase);

            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
        }
        if (internalKnowledgeBase==null) {
            return  null;
        }
        return  internalKnowledgeBase.newKieSession();
    }

    @Override
    public KieSession getDroolsRuleBybusinessIdAndGroup(String businessId ,String group) {
        if (concurrentHashMap.size()>200) { //防止内存溢出
            concurrentHashMap.clear();//清空缓存
        }
        InternalKnowledgeBase internalKnowledgeBase = concurrentHashMap.get(businessId+group);
        if (internalKnowledgeBase==null) {
            try {
                KnowledgeBuilder kb = KnowledgeBuilderFactory.newKnowledgeBuilder();
                //装入规则,可以装入多个
                List<DroolsRule> droolsRuleList = droolsRuleDao.getDroolsRuleBybusinessIdAndGroup(businessId,group);
                for (DroolsRule financeDroolsRule : droolsRuleList) {
                    kb.add(ResourceFactory.newByteArrayResource(financeDroolsRule.getRuleContent().getBytes("utf-8")), ResourceType.DRL);
                }
                internalKnowledgeBase = KnowledgeBaseFactory.newKnowledgeBase();
                internalKnowledgeBase.addPackages(kb.getKnowledgePackages());
                concurrentHashMap.put(businessId+group,internalKnowledgeBase);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        if (internalKnowledgeBase==null) {
            return  null;
        }
        return   internalKnowledgeBase.newKieSession();
    }

    /** * 执行全部的规则 * @param kieSession * @return */
    public int fireAllRules(KieSession kieSession){
        int i = kieSession.fireAllRules();
        kieSession.destroy();
        return i;
    }

    /** * 执行名称全匹配的规则 * @param kieSession * @param name * @return */
    public int fireAllRulesFullByName(KieSession kieSession,String name){
        int i = kieSession.fireAllRules(new RuleNameEqualsAgendaFilter(name));
        kieSession.destroy();
        return i;
    }

    /** * 执行名称匹配正则的规则 * @param kieSession * @param name * @return */
    public int fireAllRulesMatcheByName(KieSession kieSession,String name){
        int i = kieSession.fireAllRules(new RuleNameMatchesAgendaFilter(name));
        kieSession.destroy();
        return i;
    }

    /** * 执行名称以xxx名称开头的规则 * @param kieSession * @param name * @return */
    public int fireAllRulesFrontByName(KieSession kieSession,String name){
        int i = kieSession.fireAllRules(new RuleNameStartsWithAgendaFilter(name));
        kieSession.destroy();
        return i;
    }

    /** * 执行名称以xxx名称结尾的规则 * @param kieSession * @param name * @return */
    public int fireAllRulesEndByName(KieSession kieSession,String name){
        int i = kieSession.fireAllRules(new RuleNameEndsWithAgendaFilter(name));
        kieSession.destroy();
        return i;
    }

}

DroolsJunitTest

import com.drools.BlogAdminApplication;
import com.drools.entity.Customer;
import com.drools.entity.Order;
import com.drools.entity.User;
import com.drools.filter.TestAgendaFilter;
import com.drools.service.impl.DroolsRuleServiceImpl;
import org.drools.core.base.RuleNameEndsWithAgendaFilter;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.kie.api.runtime.KieSession;
import org.kie.api.runtime.StatelessKieSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.ArrayList;
import java.util.List;

@RunWith(SpringRunner.class)
@SpringBootTest(classes = BlogAdminApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class DroolsJunitTest {
    
    @Autowired
    private  DroolsRuleServiceImpl droolsRuleService;
    @Test
    public void test1() throws InterruptedException {
        
        long startTime = System.currentTimeMillis(); //获取开始时间
        for (int i = 0; i < 10000; i++) {
            KieSession kieSession = droolsRuleService.getDroolsRuleBybusinessIdAndGroup("0001","a");
            droolsRuleService.fireAllRulesFullByName(kieSession,"rules1");
        }
        long endTime = System.currentTimeMillis(); //获取结束时间
        System.out.println("程序运行时间:" + (endTime - startTime) + "ms"); //输出程序运行时间
    }

}

点赞 -收藏-关注-便于以后复习和收到最新内容有其他问题在评论区讨论-或者私信我-收到会在第一时间回复如有侵权,请私信联系我感谢,配合,希望我的努力对你有帮助^_^

相关文章