写点什么

Drools 规则引擎实践直白总结

  • 2022 年 4 月 30 日
  • 本文字数:7122 字

    阅读完需:约 23 分钟

示例代码:


accumulate(Message(createBy=="zuowj",countNum:count(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 的语法结构:


from accumulate(


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


示例代码:


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;


  1. 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,通过该对象可以使用更多的操 《一线大厂 Java 面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义》无偿开源 威信搜索公众号【编程进阶路】 作当前 Working Memory 的方法;同时 Drools 还提供了一个名为 kcontext 的宏对象,使我们可以通过该对象直接访问当前 Working Memory 的 KnowledgeRuntime。另外,通过注册 Channel 实现命中规则后通过 channels[通道名].send 发送消息,并传递给 JAVA 代码的订阅回调方法;

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

  3. query:查询是一种搜索工作内存中与指定条件匹配的事实的简单方法。因此,它只包含规则的


LHS 的结构,因此既不指定“when”也不指定“then”。查询具有可选的参数集,每个参数


可以可选地键入。如果未给出类型,则假定类型为 Object。引擎将根据需要尝试强制值。


查询名称对于 KieBase 是全局的;因此不要向同一 RuleBase 的不同包添加相同名称的查询。


要返回结果,请使用 ksession.getQueryResults(“name”),其中“name”是查询


的名称。这将返回查询结果的列表,这允许您检索与查询匹配的对象。查询以 query 关键字开始,以 end 关键字结束,在 package 当中一个查询要有唯一的名称,查询的内容就是查询的条件部分,条件部分内容的写法与规则的 LHS 部分写法非常相同。


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

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

  3. Salience 优先级,作用是用来设置规则执行的 优先级, salience 属性的值是一个数字,数字越大执行优先级越高,同时它的值可以是一个负数。默认情况下,规则的 salience 默认值为 0;

  4. no-loop 防止死循环,作用是用来控制已经执行过的规则在条件再次满足时是否再次执行,no-loop 属性的值是一个布尔型,默认情况下规则的 no-loop 属性的值为 false,如果 no-loop 属性值为 true,那么就表示该规则只会被引擎检查一次,如果满足条件就执行规则的 RHS 部分;

  5. date- effective 日期比较小于等于,该属性是用来控制规则只有在到达后才会触发,在规则运行时,引擎会自动拿当前操作系统的时候与 date-effective 设置的时间值进行比对,只有 当前系统时间>=date-effective 设置的时间值时,规则才会触发执行,否则执行将不执行;

  6. date- exspires 日期比较大于,该属性的作用与 date-effective 属性恰恰相反,当前系统时间<date-expires 值,date-expires 的作用是用来设置规则的有效期,引擎在执行规则的时候,会检查规则有没有 date-expires 属性,如果有的话,那么会将这个属性的值与当前系统时间进行比对,如果大于系统时间,那么规则就执行,否则就不执行;

  7. Dialect 方言,该属性用来定义规则当中要使用的语言类型,支持 mvel 和 java,默认是 java;

  8. Enabled 是否可用,用来定义一个规则是否可用的,如是设为 false 则不会执行该规则,默认为 true;

  9. lock- on-active 规则只执行一次,当在规则上使用 ruleflow-group 属性或 agenda-group 属性的时候,将 lock-on-action 属性的值设置为 true,可能避免因某些 Fact 对象被修改而使已经执行过的规则再次被激活执行;

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

  11. 其它的:agenda- group 议程分组、auto-focus 焦点分组;

  12. ruleflow-group 规则流,在使用规则流的时候要用到 ruleflow-group 属性,该属性的值为一个字符串,作用是用来将规则划分为一个个的组,然后在规则流当中通过使用 ruleflow-group 属性的值,从而使用对应的规则,该属性会通过流程的走向确定要执行哪一条规则。在规则流中有具体的说明。

  13. drools 中相关核心类型说明:

  14. fact:即将一个普通的 JavaBean 插入到规则的 WorkingMemory 当中后的对象(如:kieSession.insert( javaBean 对象))。规则可以对 Fact 对象进行任意的读写操作,当一个 JavaBean 插入到 workingMemory 当中变成 Fact 之后(返回一个 FactHandler),Fact 对象不是原来的 JavaBean 对象的副本,而是原来 JavaBean 对象的引用;

  15. KieServices:就是一个中心,通过它来获取的各种对象来完成规则构建、管理和执行等操作;(KieServices.Factory.get() 获得)

  16. KieContainer:是一个 KieBase 的容器,利用 KieContainer 来访问 KBase 和 KSession 等信息;(KieServices.newKieContainer()获得)

  17. KieBase:可以理解为是一个知识仓库,包含了若干的规则、流程、方法等,在 Drools 中主


要就是规则和方法,KieBase 本身并不包含运行时的数据之类的,如果需要执行规则 KieBase


中的规则的话,就需要根据 KieBase 创建 KieSession;(KieContainer.getKieBase() 或 newKieBase()获得)


  1. KieSession:就是一个跟 Drools 引擎打交道的会话,基于 KieBase 创建,它会包含运行时数据,包含“事实 Fact”,并对运行时数据事实进行规则运算;分为两类:有状态的 KieSession(在多次与规则引擎进行交互中,维护会话的状态)、无状态的 StatelessKieSession(隔离了每次与规则引擎的交互,不会维护会话的状态);(KieBase.newStatelessKieSession() 或 newKieSession()获得)

  2. KieRepository:是一个单例对象,它是一个存放 KieModule 的仓库;

  3. KieProject:KieContainer 通过 KieProject 来初始化、构造 KieModule,并将 KieModule 存放到 KieRepository 中,然后 KieContainer 可以通过 KieProject 来查找 KieModule 定义的信息,并根据这些信息构造 KieBase 和 KieSession;

  4. ClasspathKieProject:ClasspathKieProject 实现了 KieProject 接口,它提供了根据类路径中的 META-INF/kmodule.xml 文件构造 KieModule 的能力,也就是我们能够基于 Maven 构造 Drools 组件的基本保障之一;


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


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


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" +


"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);


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


//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 等)


//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);


  1. 直接通过 drools spring 配置文件实现规则添加及运行:


<?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 代码:


//配置,此处只需通过 @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 方法 ...


}


  1. 还有一种是通过动态创建 Kjar 来实规则添加及运行,关键步骤如下:


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


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


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


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" +


"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"+


"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",countNum:count(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 = {FmsHelperApplication.class}, webEnvironment = SpringBootTest.WebEnvironment.NONE)


public class RuleTests {


@Autowired


private RuleDemo ruleDemo;


@Test


public void testRule() {

用户头像

还未添加个人签名 2022.04.13 加入

还未添加个人简介

评论

发布
暂无评论
Drools规则引擎实践直白总结_Java_爱好编程进阶_InfoQ写作社区