Drools规则引擎实践直白总结

WenJun.Zuo ITer

Drools规则引擎,网上大把相关的文章介绍,但我感觉不够直白,理解有些困难,且知识点没有集中比较分散、有些还是早前版本的内容,对与新手来说上手可能比较慢,而且比较容易走弯路,故我在深入研究并实践于项目中后,在空闲时间花费精力整理了这篇文章,分享出来,便大家快速上手。(最早发表在我的博客园博客中)

1. 创建Drools环境(引入Drools相关依赖包、现在都流行spring boot,故最简单有效的依赖才是最好的,kie-spring内部自行依赖了drools相关核心的依赖包)

1
2
3
4
5
<dependency>
<groupId>org.kie</groupId>
<artifactId>kie-spring</artifactId>
<version>7.55.0.Final</version>
</dependency>

2. 了解Drools语法及其含义(LHS、RHS、Fact)

  1. DRL文件基本格式:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    package rules.testwrod //包名,必需,这是逻辑上,与物理路径无关
    import xxxxx; //可选,导入要使用的类名(还支持直接导入静态方法)
    global java.util.List myGlobalList;//可选,定义全局变量(该变量由外部setGlobal传入)

    function getResult(...){ //可选,自定义函数

    }

    query "query_gt_0"(...) //可选,自定义查询(仅只有LHS内容)
    $result:规则Pattern
    end

    rule “test001” //规则名称,必需,且需唯一
    when //规则开始关键字,必需
    //这里如果为空 则表示 eval(true); LHS内容(即:规则条件)
    then //规则条件结束关键字,必需,后面部份则是RHS内容(即:规则触发的逻辑)
    System.out.println(“hello drools!”);
    end //规则结束关键字
  2. 涉及的名词解释:

    1. LHS:条件部分又被称之为 Left Hand Side,简称为 LHS,在一个规则当中 when 与 then 中
      间的部分就是 LHS 部分。在 LHS 当中,可以包含 0~n 个条件,如果 LHS 部分没空的话,
      那么引擎会自动添加一个 eval(true)的条件,由于该条件总是返回 true,所以 LHS 为空的规
      则总是返回 true。LHS涉及的匹配元素用法如下:

      • Pattern 模式,语法:事实类型(约束),其中约束是可选的,如:Person(age>18),意思是:匹配工作内存中是Person类型且age>18,若存在则为true,即命中该条规则;Pattern 模式支持多个,之间使用空格或换行即可;【通俗点:当前工作内存中有没有这个类型的对象(fact)】

      • 字段约束,即Pattern 模式中括号中的部份,一般有:单值限制(如:age>18)、复合值限制(Person(sex in (0,1)),注:暂支持in与not in)和多限制(如:age>18 && age<30 或 age ( (> 30 && < 40) || (> 20 && < 25) )) 3种限制模式;字段约束之间支持:||、&&、and、or、,(逗号即为AND)【通俗点:当前工作内中的这个类型(fact)的对象属性还需满足相关的约束条件】

      • 条件元素 eval,条件元素 eval 本实上是包罗万象的,它允许执行任何语义代码(返回一个 boolean 原型)【通俗点:动态解释执行代码逻辑,与js的eval有类似功能】

      • 条件元素 not,用于检查在工作内存中不存在某东西。把”not”看作“一定没有……”的意思

      • 条件元素 exists,用于检查在工作内存中存在某类型(fact)。把”exists”看作“至少有一个……”的意思。(如果匹配到多个事实fact对象,也只会触发执行一次RHS中逻辑)

      • 条件元素 forall,用于检查在工作内存中所有的对象(fact)都必需满足Pattern 模式,若有1个不满足,则为false,只有全部满足才为true;(如果匹配到多个事实fact对象,也只会触发执行一次RHS中逻辑)

      • 条件元素 from, 让用户指定任意的资源,用于 LHS 模式的数据匹配。这允许引擎在非工作内存数据的基础上进行推断。数据源可以是一个绑定变量的一个子字段,或者方法调用的结果。它是一个超强结构,允许开箱即可与其他应用程序组件或框架集成使用【通俗点:from后面是指定一个自定义的数据源,from前面是from后面结果得到的,类似sql中的select field=value from table;】

      • 条件元素 collect,允许规则在来自特定资源或工作内存的一个对象集合上进行推断【通俗点:就是将符合匹配到多个事实fact对象累加到一起形成一个Collection集合】

      • 条件元素 accumulate,是一个更灵活强大的 collect 形式,它主要做的事是允许规则迭代整个
        一个对象的集合,为每个元素定制执行动作,并在结束时返回一个结果对象, accumulate
        既支持预定义的累积函数的使用,或也可以使用内联的自定义代码,简化的语法如下:

        accumulate( < source pattern 源模式 >; < functions 函数 > [;< constraints >] ),其中函数除了内置的还可以自定义JAVA函数,只需使用import accumulate 类型(该类型需实现AccumulateFunction接口) 自定义方法名;

        示例代码:

        1
        2
        3
        accumulate(Message(createBy=="zuowj",$id:id);$countNum:count($id);$countNum>1)
        //含义:查找工作内存中有Message类型的且过滤条件为(createBy=="zuowj")fact事实对象,并取出id,然后对所有的id进行count,最后判断count的结果是否>1,转换为SQL理解就是:
        //select id from Message where createBy='zuowj' group by id having count(id)>1;这样应该好理解吧!

        inline 的语法结构:
        < result pattern >from accumulate(< source pattern >,init(< init code >),action(< action
        code >),reverse(< reverse code >),result(< result expression >) )

        < source pattern >:这个表示源模式。用法:也就是我们常用手 Object(xx:XX 属性) 这个会去匹配每一个源对象;
        < init code >:用法说明:init 是做初始化用的,简单的说,在 source pattern 遍历完之后 就已经触发,有点像 for 的开头;
        < action code >: 用法说明:action 会执行所以满足条件的源对象进行操作,像是 for的方法体。在里面可写 java code;
        < reverse code >: 这是一个可选的被选方言的语义代码块,如果存在,将为不再匹配资源模式的每个资源对象执行。这个代码块的目的是不做在< action code > 块中做的任何计算,所以,当一个资源对象被修改或删除收时,引擎可能做递减计算,极大地提升了这些操作的性能;
        < result expression >: 返回值,是根据 action 上面两个遍历出来的结果进行一个返
        回,这个返回值中也可以进行计算。
        < result pattern >: 返回值类型,在< result expression >返回值的类型再一次进行匹
        配,如果匹配不成功则返回 false。

        示例代码:

        1
        2
        3
        4
        5
        6
        7
        8
        9
        $res:String() from accumulate(Message(createBy=="zuowj",$cont:content),init(String allContent="";),action(allContent +=$cont;),result(allContent))
        //含义:for循环遍历工作内存中Message类型且过滤条件为(createBy=="zuowj")的fact对象,初始化设置allContent="",每次执行allContent +=$cont,遍历完成后将allContent返回给#res变更接收,类似JAVA for代码如下:
        // String res="",allContent="";
        //for (Object o:List<Object>){
        // if(o instanceof Message && ((Message)o).getContent()=="zuowj"){
        // allContent+=((Message)o).getContent();
        // }
        //}
        //res=allContent;
    2. RHS:结果部分又被称之为 Right Hand Side,简称为 RHS,在一个规则当中 then 后面部分就是 RHS,只有在 LHS 的所有条件都满足时 RHS 部分才会执行。RHS 部分是规则真正要做事情的部分,可以将因条件满足而要触发的动作写在该部分当中,在 RHS 当中可以使用 LHS 部分当中定义的绑定变量名、设置的全局变量、或者是直接编写 Java 代码(对于要用到的 Java 类,需要在规则文件当中用 import 将类导入后方能使用,这点和 Java 文件的编写规则相同,且不建议在RHS中写条件判断,如果需要条件判断,那么请重新考虑将其放在 LHS 当中,否则就违背了使用规则的初衷。),同时在 RHS 里面,还提供了一些对当前 Working Memory 实现快速操作的宏函数或对象,比如 insert/insertLogical、update/modify 和 retract 就可以实现对当前 WorkingMemory 中的 Fact 对象进行新增、修改或者是删除;如果您觉得还要使用 Drools 当中提供的其它方法,那么您还可以使用另一外宏对象 drools,通过该对象可以使用更多的操作当前 Working Memory 的方法;同时 Drools 还提供了一个名为 kcontext 的宏对象,使我们可以通过该对象直接访问当前 Working Memory 的 KnowledgeRuntime。另外,通过注册Channel实现命中规则后通过channels[通道名].send发送消息,并传递给JAVA代码的订阅回调方法;

    3. function:函数类似JAVA中的有返回值的方法,将RHS中涉及一些重复的动作封装定义成函数(支持定义入参),能够有效的简少重复逻辑的编写,但注意,函数的应用是不建议直接写在LHS块中的,若需使用需使用eval关键字,类似:eval(hello("梦在旅途"));

    4. query:查询是一种搜索工作内存中与指定条件匹配的事实的简单方法。因此,它只包含规则的
      LHS 的结构,因此既不指定“when”也不指定“then”。查询具有可选的参数集,每个参数
      可以可选地键入。如果未给出类型,则假定类型为 Object。引擎将根据需要尝试强制值。
      查询名称对于 KieBase 是全局的;因此不要向同一 RuleBase 的不同包添加相同名称的查询。
      要返回结果,请使用 ksession.getQueryResults(“name”),其中“name”是查询
      的名称。这将返回查询结果的列表,这允许您检索与查询匹配的对象。查询以 query 关键字开始,以 end 关键字结束,在 package 当中一个查询要有唯一的名称,查询的内容就是查询的条件部分,条件部分内容的写法与规则的 LHS 部分写法非常相同。

    5. global:全局变量(类似java中的final static定义的变量 ),同一个session中所有rule都共享使用(如果多个包使用相同的标识符声明了全局变量,那么它们必须有相同的类型,并且它们所有都会引用相同的全局变量的值),全局变量没有被插入到工作内存,因此,全局变量绝不能被用来在规则中建立条件,除非它是一个恒定不变的值。引擎不能知道全局变量的改变,不能跟踪它们的变化。还需注意:常量值是不能改变的、包装类是不能改变的、类似 javaBean,List 这类的操作,是可以改变内容的,但内存地址不会变;

  3. Drools的属性说明(一般在在rule 名称 与when之前设置属性):

    1. Salience 优先级,作用是用来设置规则执行的 优先级, salience 属性的值是一个数字,数字越大执行优先级越高,同时它的值可以是一个负数。默认情况下,规则的 salience 默认值为 0;
    2. no-loop 防止死循环,作用是用来控制已经执行过的规则在条件再次满足时是否再次执行,no-loop 属性的值是一个布尔型,默认情况下规则的no-loop 属性的值为 false,如果 no-loop 属性值为true,那么就表示该规则只会被引擎检查一次,如果满足条件就执行规则的 RHS 部分;
    3. date- effective 日期比较小于等于,该属性是用来控制规则只有在到达后才会触发,在规则运行时,引擎会自动拿当前操作系统的时候与 date-effective 设置的时间值进行比对,只有 当前系统时间>=date-effective 设置的时间值时,规则才会触发执行,否则执行将不执行;
    4. date- exspires 日期比较大于,该属性的作用与 date-effective 属性恰恰相反,当前系统时间<date-expires 值,date-expires 的作用是用来设置规则的有效期,引擎在执行规则的时候,会检查规则有没有 date-expires 属性,如果有的话,那么会将这个属性的值与当前系统时间进行比对,如果大于系统时间,那么规则就执行,否则就不执行;
    5. Dialect 方言,该属性用来定义规则当中要使用的语言类型,支持 mvel 和 java,默认是java;
    6. Enabled 是否可用,用来定义一个规则是否可用的,如是设为false则不会执行该规则,默认为true;
    7. lock- on-active 规则只执行一次,当在规则上使用 ruleflow-group属性或 agenda-group 属性的时候,将 lock-on-action属性的值设置为 true,可能避免因某些 Fact 对象被修改而使已经执行过的规则再次被激活执行;
    8. activation-group 分组,作用是将若干个规则划分成一个组,用一个字符串来给这个组命名,这样在执行的时候, 具有相同 activation- - group 属性的规则中只要有一个会被执行,其它的规则都将不再执行;
    9. 其它的:agenda- group 议程分组、auto-focus 焦点分组;
    10. ruleflow-group 规则流,在使用规则流的时候要用到 ruleflow-group 属性,该属性的值为一个字符串,作用是用来将规则划分为一个个的组,然后在规则流当中通过使用 ruleflow-group 属性的值,从而使用对应的规则,该属性会通过流程的走向确定要执行哪一条规则。在规则流中有具体的说明。
  4. drools中相关核心类型说明:

    1. fact:即将一个普通的 JavaBean 插入到规则的 WorkingMemory 当中后的对象(如:kieSession.insert( javaBean对象))。规则可以对 Fact对象进行任意的读写操作,当一个 JavaBean 插入到 workingMemory 当中变成 Fact 之后(返回一个FactHandler),Fact 对象不是原来的 JavaBean对象的副本,而是原来 JavaBean 对象的引用;
    2. KieServices:就是一个中心,通过它来获取的各种对象来完成规则构建、管理和执行等操作;(KieServices.Factory.get() 获得)
    3. KieContainer:是一个 KieBase 的容器,利用 KieContainer 来访问 KBase 和 KSession 等信息;(KieServices.newKieContainer()获得)
    4. KieBase:可以理解为是一个知识仓库,包含了若干的规则、流程、方法等,在 Drools 中主
      要就是规则和方法,KieBase 本身并不包含运行时的数据之类的,如果需要执行规则 KieBase
      中的规则的话,就需要根据 KieBase 创建 KieSession;(KieContainer.getKieBase() 或 newKieBase()获得)
    5. KieSession:就是一个跟 Drools 引擎打交道的会话,基于 KieBase 创建,它会包含运行时数据,包含“事实 Fact”,并对运行时数据事实进行规则运算;分为两类:有状态的 KieSession(在多次与规则引擎进行交互中,维护会话的状态)、无状态的 StatelessKieSession(隔离了每次与规则引擎的交互,不会维护会话的状态);(KieBase.newStatelessKieSession() 或 newKieSession()获得)
    6. KieRepository:是一个单例对象,它是一个存放 KieModule 的仓库;
    7. KieProject:KieContainer 通过 KieProject 来初始化、构造 KieModule,并将 KieModule 存放到 KieRepository 中,然后 KieContainer 可以通过 KieProject 来查找 KieModule 定义的信息,并根据这些信息构造 KieBase 和KieSession;
    8. ClasspathKieProject:ClasspathKieProject 实现了 KieProject 接口,它提供了根据类路径中的 META-INF/kmodule.xml 文件构造 KieModule 的能力,也就是我们能够基于 Maven 构造 Drools 组件的基本保障之一;

3. 几种实现运行Drools规则引擎方法

  1. 直接使用KieHelper动态的将规则drl字符串添加到规则引擎中并运行:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    String drl = "package zuowenjun.drools.rule.demo\n" +
    "import cn.zuowenjun.model.Message;\n" +
    "import java.util.List;\n" +
    "rule \"test rule 1\"\n" +
    "when \n" +
    "$res:String() from accumulate(Message(createBy==\"zuowj\",$cont:content),init(String allContent=\"\";),action(allContent +=$cont;),result(allContent))"+
    "then\n" +
    "System.out.println($res +\"---rule 2\");\n" +
    "end";

    KieBase kieBase = new KieHelper().addContent(drl, ResourceType.DRL).build();
    StatelessKieSession kieSession = kieBase.newStatelessKieSession();
    kieSession.execute(list);
  2. 直接使用KieHelper动态的将drl文件添加到规则引擎中并运行:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    //rule.drl文件(放在resources自定义rules目录中,注:路径可自定义)
    package zuowenjun.drools.rule.demo
    import cn.zuowenjun.model.Message;

    rule "test rule2"
    when
    $msg:Message(createBy=="zuowj")
    then
    System.out.println("hello zuowj! --rule2");
    $msg.setReplyBy("rule2");
    end

    注:如下使用的是ResourceFactory.newClassPathResource获取drl文件,其实里面封装了很多的获取资源的方式(如:newFileResource、newByteArrayResource、newInputStreamResource等)

    1
    2
    3
    4
    5
    6
    //JAVA代码:
    Resource resource = ResourceFactory.newClassPathResource("rules/rule.drl");
    KieHelper helper = new KieHelper();
    KieBase kieBase = helper.addResource(resource, ResourceType.DRL).build();
    StatelessKieSession kieSession = kieBase.newStatelessKieSession();
    kieSession.execute(msg);
  3. 直接通过drools spring配置文件实现规则添加及运行:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <!--在resources目录中添加drools spring的配置文件(如:spring-drools.xml) -->
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:kie="http://drools.org/schema/kie-spring"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
    http://drools.org/schema/kie-spring http://drools.org/schema/kie-spring.xsd">
    <kie:kmodule id="kmodule">
    <kie:kbase name="kbase" packages="zuowenjun.drools.rules">
    </kie:kbase>
    </kie:kmodule>
    <bean id="kiePostProcessor" class="org.kie.spring.KModuleBeanFactoryPostProcessor"/>
    </beans>

    JAVA代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    //配置,此处只需通过@ImportResource导入配置文件,自动注册成BEAN即可,当然这里是一个单独配置文件,实际也可以直接放在spring boot 的applcation的启动类上即可。
    @Configuration
    @ImportResource("classpath:spring-drools.xml")
    public class DroolsBeansConfig {

    }

    //BEAN类中直接使用即可
    @Component
    public class RuleDemo {
    @Autowired
    private KieBase kbase;//KieBase是单例

    public Object checkRule(Message msg){
    StatelessKieSession kieSession = kbase.newStatelessKieSession();//session这里尽可能每次都重新创建,成本也比较低,不要搞成单例的,这里是无状态的,用有状态的也行
    kieSession.execute(msg);
    return msg;
    }

    }

    //如下是上面所有实例中用到的Message类(普通的javaBean)
    public class Message {
    private Long id;
    private String title;
    private String createBy;
    private Date createDate;
    private String content;
    private Long enabledFlag;
    private Boolean isReply;
    private String replyBy;
    private Date replyDate;
    private String replyContent;
    //省略getter、setter方法 ...
    }
  4. 还有一种是通过动态创建Kjar来实规则添加及运行,关键步骤如下:

    创建 pom.xml-》创建 kmodule.xml-》添加规则内容-》后面是创建session-》执行即可;

    代码就不再贴出了,可详见网上资源。

4. Drl规则内容几种写法测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
 public Object checkRule(Object msg) {
List<String> drlContentList=new ArrayList<>();

//当一个Fact对象为集合对象时的判断
//这个是当把某个集合(List)当成一个fact传入工作内存中后,规则有效
drlContentList.add("package zuowenjun.drools.rule.demo\n" +
"import cn.zuowenjun.model.Message;\n" +
"import java.util.List;\n" +
"rule \"test rule 0\"\n" +
"when \n" +
"$list:List(size>0) \n" +
"$msg:Message(createBy==\"zuowj\") from $list \n " +
"then\n" +
"System.out.println(\"hello zuowj! ---rule 0\");\n" +
"$msg.setReplyBy(\"rule 0\");\n" +
"end");

//普通Pattern 模式+字段约束
drlContentList.add("package zuowenjun.drools.rule.demo\n" +
"import cn.zuowenjun.model.Message;\n" +
"rule \"test rule 1\"\n" +
"when \n" +
"$msg:Message(createBy==\"zuowj\")\n " +
"then\n" +
"System.out.println(\"hello zuowj! ---rule 1\");\n" +
"$msg.setReplyBy(\"rule 1\");\n" +
"end");

//accumulate 内联方式(类似for循环处理)
drlContentList.add("package zuowenjun.drools.rule.demo\n" +
"import cn.zuowenjun.model.Message;\n" +
"rule \"test rule 2\"\n" +
"when \n" +
"exists(Message(createBy==\"zuowj\"))\n"+
"$res:String() from accumulate(Message(createBy==\"zuowj\",$cont:content),init(String allContent=\"\";),action(allContent +=$cont;),result(allContent))"+
"then\n" +
"System.out.println($res +\"---rule 2\");\n" +
"end");

//accumulate 普通函数方式
drlContentList.add("package zuowenjun.drools.rule.demo\n" +
"import cn.zuowenjun.model.Message;\n" +
"rule \"test rule 2-2\"\n" +
"when \n" + "accumulate(Message(createBy==\"zuowj\",$id:id);$countNum:count($id);$countNum>1) \n"+
"then\n" +
"System.out.println(\"count number:\"+ $countNum +\"---rule 2-2\");\n" +
"end");

//not,不满足时
drlContentList.add("package zuowenjun.drools.rule.demo\n" +
"import cn.zuowenjun.model.Message;\n" +
"rule \"test rule 3\"\n" +
"when not Message()\n" +
"then\n" +
"System.out.println(\"no message don't say hello! ---rule 3\");\n" +
"end");

//exists,匹配执行一次
drlContentList.add("package zuowenjun.drools.rule.demo\n" +
"import cn.zuowenjun.model.Message;\n" +
"rule \"test rule 4\"\n" +
"when exists(Message(createBy==\"zuowj\"))\n" +
"then\n" +
"System.out.println(\"exists Message(createBy==zuowj) fact! ---rule 4\");\n" +
"end");

//forall,工作内存中所有fact对象必需都满足时才匹配规则
drlContentList.add("package zuowenjun.drools.rule.demo\n" +
"import cn.zuowenjun.model.Message;\n" +
"rule \"test rule 5\"\n" +
"when forall(Message(createBy==\"zuowj\"))\n" +
"then\n" +
"System.out.println(\"for all Message(createBy==zuowj) fact! ---rule 5\");\n" +
"end");

//collect,将工作内存中所有fact对象添加到同一个集合中
drlContentList.add("package zuowenjun.drools.rule.demo\n" +
"import cn.zuowenjun.model.Message;\n" +
"rule \"test rule 6\"\n" +
"when Message() && $msgs:List(size>=9) from collect(Message(createBy==\"zuowj\"))\n" +
"then\n" +
"System.out.println(\"collect all Message fact(size=\" + $msgs.size() +\")! ---rule 6\");\n" +
"end");


KieHelper kieHelper=new KieHelper();
for(String drl:drlContentList){
kieHelper.addContent(drl,ResourceType.DRL);
}


KieBase kieBase = kieHelper.build();
StatelessKieSession kieSession = kieBase.newStatelessKieSession();
if (msg instanceof List){
kieSession.execute((List<?>)msg);
} else{
kieSession.execute(msg);
}

return msg;
}

//单元测试
/**
* @author zuowenjun
*/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = {DroolsDemoApplication.class}, webEnvironment = SpringBootTest.WebEnvironment.NONE)
public class RuleTests {

@Autowired
private RuleDemo ruleDemo;

@Test
public void testRule() {
List<Message> msgList=new ArrayList<>();
for(int i=1;i<=10;i++) {
int n=i;
Message msg = new Message() {
{
setCreateBy("zuowj");
setContent("hello drools" + String.valueOf(n));
}
};
if (n==1){
msg.setCreateBy("zuowenjun.cn");
}
msgList.add(msg);
}

Object obj = ruleDemo.checkRule(msgList);
System.out.println(JsonUtils.deserializer(obj));
}

5. 规则引擎引发的举一反三,自己实现一个规则引擎

思路:1.定义规则内容(即:规则执行单元),2.定义贯穿整个规则执行链条的上下文,内部就放fact、global等,具体实现参照如下示例代码【注意:如果仅是示例测试代码,并不规范,仅为演示提供思路】,整个规则执行采取:责任链的设计模式,即:每个规则只负责满足自己条件的执行逻辑,最后更新上下文中相关的内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//规则链上下文,里面就包含fact集合,全局对象及执行过的rule
public class RuleChainContext {
public List<Object> factList;
public static Map<String, Object> global;
public RuleUnit execedRule;
}

//规则执行单元抽象类(这里用抽象类而没有用接口,是因为我要限定组织逻辑,可以理解为模板用法)
public abstract class RuleUnit {
public RuleUnit nextExecedRule;

protected String name;

public abstract String getName();

public abstract boolean matchWhen(RuleChainContext context);

public abstract void doThen(RuleChainContext context);

public final void execute(RuleChainContext context) {
if (matchWhen(context)) {
doThen(context);
}
if (context.execedRule == null) {
context.execedRule = this;
}
context.execedRule.nextExecedRule = this;
}

}

通过单元测试模拟调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
@Test
public void testDefRules() {
List<RuleUnit> ruleUnitList = new ArrayList<>();
ruleUnitList.add(new RuleUnit() {
@Override
public String getName() {
name= "rule-1";
return name;
}

@Override
public boolean matchWhen(RuleChainContext context) {
return context.factList.stream().anyMatch(f->f instanceof Integer && 1==(Integer)f);
}

@Override
public void doThen(RuleChainContext context) {
System.out.println("rule[include 1] do");
//TODO:context
}
});

ruleUnitList.add(new RuleUnit() {
@Override
public String getName() {
name= "rule-2";
return name;
}

@Override
public boolean matchWhen(RuleChainContext context) {
return context.factList.stream().anyMatch(f->f instanceof Integer && 2==(Integer)f);
}

@Override
public void doThen(RuleChainContext context) {
System.out.println("rule[include 2] do");
//TODO:context
}
});

RuleChainContext context=new RuleChainContext();
context.factList=new ArrayList<>();
context.factList.add(1);//加入1则触发规则1
context.factList.add(2);//加入2则触发规则2,若减少规则相应减少

for(RuleUnit ruleUnit:ruleUnitList){
ruleUnit.execute(context);
}

System.out.println("result context:\n" + JsonUtils.deserializer(context));

}

最终结果:

1
2
3
4
rule[include 1] do
rule[include 2] do
result context:
{"factList":[1,2],"execedRule":{"nextExecedRule":{"nextExecedRule":null,"name":"rule-2"},"name":"rule-1"}}

从输出的结果可以看出,还是可以达到规则引擎的简单效果的,当然如果想在生产环境实际应用自己实现的“类规则引擎”代码,实现规则与执行分开,也可将规则执行单元(RuleUnit)实现类单独放到一个JAR包,然后再借助于URLClassLoader实现动态加载并添加自定义的实现规则执行单元(RuleUnit)的类,最后执行即可。【.NET方面的同学实现亦同理】

注:文中相关名词解释来源于网上,并非原创,我这里仅为知识点总结!

可参考相关drools系列文章:

Drools_miemieY89-CSDN博客

邵飞翔的图书馆 (360doc.com)

  • 标题: Drools规则引擎实践直白总结
  • 作者: WenJun.Zuo
  • 创建于 : 2024-03-29 22:22:01
  • 更新于 : 2024-03-29 22:22:01
  • 链接: https://www.zuowenjun.cn//2024/03/29/drools-summary/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
 评论