Drools规则引擎实践直白总结
阅读原文时间:2021年06月30日阅读:1

目录

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

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

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

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

  1. DRL文件基本格式:

    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. 涉及的名词解释:

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

  4. 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" +
                    "$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);</code></pre></li>
  2. 直接使用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);
  3. 直接通过drools spring配置文件实现规则添加及运行:

    <!--在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代码:

    //配置,此处只需通过@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规则内容几种写法测试代码

 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 = {FmsHelperApplication.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等,具体实现参照如下示例代码【注意:如果仅是示例测试代码,并不规范,仅为演示提供思路】,整个规则执行采取:责任链的设计模式,即:每个规则只负责满足自己条件的执行逻辑,最后更新上下文中相关的内容。

//规则链上下文,里面就包含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;
        }

    }

通过单元测试模拟调用:

 @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[exclude 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));

    }

最终结果:

rule[include 1] do
rule[exclude 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)

手机扫一扫

移动阅读更方便

阿里云服务器
腾讯云服务器
七牛云服务器

你可能感兴趣的文章