美文网首页DroolsDrools
drools 如何动态加载规则

drools 如何动态加载规则

作者: b424191ea349 | 来源:发表于2018-07-11 13:19 被阅读420次

    drools版本:7.0
    语言:java8
    描述:动态从数据库中加载规则,并且加载到工作内存中

    最终实现效果:从数据库中读取数据生成drl格式的字符串以后,可以一次性加载到工作内存中,也可以逐次加载到内存中进行build,而之前已经build好的规则不会消失。

    下面是具体实现过程:

    首先drools提供了常见的两种加载规则的方式,一种是通过定义kmodule.xml的方式进行加载,并且在对应的package下写好drl规则文件即可,xml文件如下,规则文件略。

        <?xml version="1.0" encoding="UTF-8"?>
        <kmodule xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xmlns="http://www.drools.org/xsd/kmodule">
        <kbase name="rule1KB" packages="rules">
            <ksession name="rule1KS"/>
        </kbase>
        </kmodule>
    

    通过这种xml加载的方式在具体java代码中获取session会话的方式是这样的:

    KieContainer kc =KieServices.Factory.get().getKieClasspathContainer("rule1KB");
    KieSession kieSession = kc.newKieSession("rule1KS");
    

    注意获取KieContainerKieSession 的名称和xml中的对应。

    但是在实际的动态加载中,drl格式的文件极有可能不是写好了的,而是从数据库中取出来数据,然后通过拼接字符串的方式动态生成的(至少我们现在这个项目就要这样做(_))。
    所以采用这种方式就有问题了,好在drools官方提供了另一种加载规则文件的方式,具体代码如下:

        public KieContainer loadForRule(String drlStr) {
            KieServices ks = KieServices.Factory.get();
            KieRepository kr = ks.getRepository();
            KieFileSystem kfs = ks.newKieFileSystem();
    
            kfs.write("src/main/resources/rules/" + drlStr.hashCode() + ".drl", drlStr);
            
            // 将KieFileSystem加入到KieBuilder
            KieBuilder kb = ks.newKieBuilder(kfs);
    
            // 编译此时的builder中所有的规则
            kb.buildAll();
            if (kb.getResults().hasMessages(Message.Level.ERROR)) {
                throw new RuntimeException("Build Errors:\n" + kb.getResults().toString());
            }
    
            return ks.newKieContainer(kr.getDefaultReleaseId());
        }
    

    通过上面这段代码实际上已经可以通过动态加载的方式拿到了一个KieContainer 类,继续通过KieContainer 获取到KieSession会话即可(实际上创建一次会话代价极低),具体一行代码如下:

    KieSession kieSession = kContainer.newKieSession();
    

    但是通过上面这段代码,每次都是重新加载,会出现这样的问题:现在加载了A规则,再去加载B规则,A规则已经不存在与工作内存中了,这个问题简单思考一下应该就是每次的Kie里面的对象都是重新new的,所以每次都是重新保存,这明显和实际要求不合,最直接想到的应该就是缓存一个KieContainer 方法,但是这种方式没有成功,最后采用全局缓存了kieFileSystemkieRepository进行处理。具体代码如下(省略了get/set方法):

    public class KieUtils {
        private static KieContainer kieContainer;
    
        private static KieSession kieSession;
    
        private static KieServices kieServices;
    
        private static KieRepository kieRepository;
    
        private static KieFileSystem kieFileSystem;
    
        public static void initAndNotClear(){
            if (Objects.isNull(kieServices)) 
                kieServices = KieServices.Factory.get();
    
            if (Objects.isNull(kieRepository)) 
                kieRepository = kieServices.getRepository();
    
            if (Objects.isNull(kieFileSystem)) 
                kieFileSystem = kieServices.newKieFileSystem();
        }
    }
    

    使用上面这个类进行缓存,将上面的代码获取对应类的地方替换回去,比如:

    KieUtils.initAndNotClear();
    KieFileSystem kfs = KieUtils.getKieFileSystem();
    

    对于这种方式的思考:
    这种方式实际上还是buildKieFileSystem 中的所有的规则,实际上是一种假的添加新的规则进原规则体系中编译的方式,但实际解决了问题,并且因为动态生成最耗时间的地方在生成字符串,这种方式字符串是不用重新生成了,一般测试编译所有规则的时间是以秒级单位的,并且RETE算法本身就是一种以空间换取时间的算法,只要在规则触发时速度够快,编译时间稍微多耗一点,还是可以接受的。

    相关文章

      网友评论

        本文标题:drools 如何动态加载规则

        本文链接:https://www.haomeiwen.com/subject/jglhpftx.html