Drools入门(四)——动态规则实现

概述

前面我们实现了对规则文件的读取和执行,也知道了规则文件的基本结构与相关函数及应用,同时也学习了规则引擎的组件及规则的加载解析过程。现在将解决最后一个问题,我们开发中的需求是不会事先写好规则文件文件在启动项目的,如果是这样当规则文件修改新增或删除时就必须重启服务器,这样才能使规则生效,这显然是不合理的,接下来我们将在《Drools入门(一)——环境搭建》的基础上进行修改,使规则引擎动态加载外部规则,并实现新增和删除功能

添加KieModuleBuilder类

新建一个包命名为builder,并在builder包下新建KieModuleBuilder.java,并将下面内容复制到该类中

package builder;

import entity.Rule;
import entity.RuleGroup;
import org.drools.compiler.compiler.io.memory.MemoryFileSystem;
import org.drools.compiler.kie.builder.impl.KieContainerImpl;
import org.drools.compiler.kie.builder.impl.KieProject;
import org.drools.compiler.kie.builder.impl.MemoryKieModule;
import org.drools.compiler.kie.builder.impl.ResultsImpl;
import org.drools.compiler.kproject.models.KieBaseModelImpl;
import org.drools.compiler.kproject.models.KieModuleModelImpl;
import org.kie.api.KieServices;
import org.kie.api.builder.KieFileSystem;
import org.kie.api.builder.KieRepository;
import org.kie.api.builder.ReleaseId;
import org.kie.api.builder.model.KieBaseModel;
import org.kie.api.builder.model.KieModuleModel;
import org.kie.api.builder.model.KieSessionModel;
import org.kie.api.runtime.KieContainer;
import org.kie.api.runtime.StatelessKieSession;

import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

public class KieModuleBuilder {

    private static final String RESOURCES_ROOT = "src/main/resources/";

    private static final String FILE_SEPARATOR = "/";

    private static final String PACKAGE_NAME_PREFIX = "com.dome.rules.id_";

    private static final String PACKAGE_PATH_PREFIX = PACKAGE_NAME_PREFIX.replaceAll("\\.", FILE_SEPARATOR);

    private static final String KIE_BASE_MODEL_NAME_PREFIX = "kieBaseModelName_";

    private static final String KIE_SESSION_MODEL_NAME_PREFIX = "kieSessionModelName_";

    private static final String PACKAGE = "package ";

    private static final String FILE_SUFFIX = ".drl";

    /**
     * 创建模块化组件并添加到初始化文件系统中,初始化文件系统在完成加载后将摒弃
     * @param kieServices
     * @param kieFileSystem
     * @param ruleGroups
     * @return
     */
    public static KieModuleModel initAndBuildKieModuleModel(KieServices kieServices, KieFileSystem kieFileSystem, List ruleGroups){
        boolean isDefault = true;
        KieModuleModel kieModuleModel = kieServices.newKieModuleModel();
        for (RuleGroup ruleGroup : ruleGroups) {
            if (ruleGroup.getRules() != null && ruleGroup.getRules().size() > 0){
                buildKieBaseModel(kieModuleModel, ruleGroup, isDefault);
                isDefault = false;
            }
        }
        kieFileSystem.write(KieModuleModelImpl.KMODULE_SRC_PATH,kieModuleModel.toXML());
        return kieModuleModel;
    }

    /**
     * 添加规则文件到初始化文件系统中,初始化文件系统在完成加载后将摒弃
     * @param kieFileSystem
     * @param ruleGroups
     * @return
     */
    public static List initAndBuildRules(KieFileSystem kieFileSystem, List ruleGroups){
        List rules = new ArrayList<>();
        for (RuleGroup ruleGroup : ruleGroups) {
            for (Rule rule : ruleGroup.getRules()) {
                String ruleContent = buildRule(ruleGroup, rule);
                rules.add(ruleContent);
                kieFileSystem.write(RESOURCES_ROOT + getRuleFilePath(ruleGroup.getId(), rule.getId()), ruleContent);
            }
        }
        return rules;
    }

    /**
     * 初始化session组件,用于规则引擎使用入口
     * @param kieContainer
     * @param ruleGroups
     * @return
     */
    public static Map initStatelessKieSession(KieContainer kieContainer, List ruleGroups){
        Map statelessKieSessionMap = new LinkedHashMap<>();
        for (RuleGroup promotionRule : ruleGroups) {
            statelessKieSessionMap.put(promotionRule, buildKieSession(kieContainer, promotionRule));
        }
        return statelessKieSessionMap;
    }

    /**
     * 检查规则库中是否已经存在该规则模块
     * @param kieRepository
     * @param ruleGroup
     * @param releaseId
     * @return
     */
    public static Boolean checkBaseModule(KieRepository kieRepository, RuleGroup ruleGroup, ReleaseId releaseId){
        MemoryKieModule kieModule = (MemoryKieModule)kieRepository.getKieModule(releaseId);
        return kieModule.getKieModuleModel().getKieBaseModels().containsKey(getKieBaseModelName(ruleGroup.getId()));
    }

    /**
     * 添加规则文件及创建模块化组件并添加到文件系统中,此处为后期动态添加,该文件系统与初始化时的文件系统不是同一个
     * @param kieContainer
     * @param kieRepository
     * @param ruleGroup
     * @return
     */
    public static StatelessKieSession addBaseModuleAndRule(KieContainer kieContainer, KieRepository kieRepository, RuleGroup ruleGroup){
        MemoryKieModule kieModule = (MemoryKieModule)kieRepository.getKieModule(kieContainer.getReleaseId());
        MemoryFileSystem memoryFileSystem = kieModule.getMemoryFileSystem();
        for (Rule rule : ruleGroup.getRules()) {
            String ruleContent = buildRule(ruleGroup, rule);
            memoryFileSystem.write(getRuleFilePath(ruleGroup.getId(), rule.getId()), ruleContent.getBytes(Charset.defaultCharset()));
        }
        KieBaseModel kieBaseModel = buildKieBaseModel(kieModule.getKieModuleModel(), ruleGroup, false);
        KieProject kieProject = ((KieContainerImpl) kieContainer).getKieProject();
        ((KieContainerImpl) kieContainer).updateToKieModule(kieModule);
        kieProject.buildKnowledgePackages((KieBaseModelImpl) kieBaseModel,new ResultsImpl());
        return buildKieSession(kieContainer, ruleGroup);
    }

    /**
     * 从文件系统中删除规则文件及删除模块化组件,此处为后期动态添加,该文件系统与初始化时的文件系统不是同一个
     * @param kieContainer
     * @param kieRepository
     * @param ruleGroup
     */
    public static void removeBaseModuleAndRule(KieContainer kieContainer, KieRepository kieRepository, RuleGroup ruleGroup){
        MemoryKieModule kieModule = (MemoryKieModule)kieRepository.getKieModule(kieContainer.getReleaseId());
        MemoryFileSystem memoryFileSystem = kieModule.getMemoryFileSystem();
        for (Rule rule : ruleGroup.getRules()) {
            memoryFileSystem.remove(getRuleFilePath(ruleGroup.getId(), rule.getId()));
        }
        String kieBaseModelName = getKieBaseModelName(ruleGroup.getId());
        String kieSessionModelName = getKieSessionModelName(ruleGroup.getId());
        KieModuleModel kieModuleModel = kieModule.getKieModuleModel();
        kieModuleModel.getKieBaseModels().get(kieBaseModelName).removeKieSessionModel(kieSessionModelName);
        kieModuleModel.removeKieBaseModel(kieBaseModelName);
        //伪删除builder,其实删不删都一样,同名时会覆盖,源码也没有提供删除方法
        kieModule.cacheKnowledgeBuilderForKieBase(kieBaseModelName,null);
        kieModule.getKnowledgeResultsCache().remove(kieBaseModelName);
        ((KieContainerImpl) kieContainer).updateToKieModule(kieModule);
    }

    /**
     * 规则构建
     * @param ruleGroup
     * @param rule
     * @return
     */
    private static String buildRule(RuleGroup ruleGroup, Rule rule){
        StringBuilder ruleContent = new StringBuilder(PACKAGE);
        ruleContent.append(getPackageName(ruleGroup.getId())).append("\n");
        ruleContent.append(rule.getContent());
        return ruleContent.toString();
    }

    /**
     * Base模块构建
     * @param kieModuleModel
     * @param ruleGroup
     * @param isDefault
     * @return
     */
    private static KieBaseModel buildKieBaseModel(KieModuleModel kieModuleModel, RuleGroup ruleGroup, Boolean isDefault){
        KieBaseModel kieBaseModel = kieModuleModel.newKieBaseModel(getKieBaseModelName(ruleGroup.getId()));
        kieBaseModel.setDefault(isDefault);
        kieBaseModel.addPackage(getPackageName(ruleGroup.getId()));
        //只要无状态session
        KieSessionModel kieSessionModel = kieBaseModel.newKieSessionModel(getKieSessionModelName(ruleGroup.getId()));
        kieSessionModel.setDefault(isDefault);
        kieSessionModel.setType(KieSessionModel.KieSessionType.STATELESS);
        return kieBaseModel;
    }

    private static StatelessKieSession buildKieSession(KieContainer kieContainer, RuleGroup ruleGroup){
        return kieContainer.newStatelessKieSession(getKieSessionModelName(ruleGroup.getId()));
    }

    private static String getPackageName(String id){
        return PACKAGE_NAME_PREFIX + id;
    }

    private static String getRuleFilePath(String id, String ruleId){
        return PACKAGE_PATH_PREFIX + id + FILE_SEPARATOR + ruleId + FILE_SUFFIX;
    }

    private static String getKieBaseModelName(String id){
        return KIE_BASE_MODEL_NAME_PREFIX + id;
    }

    private static String getKieSessionModelName(String id){
        return KIE_SESSION_MODEL_NAME_PREFIX + id;
    }

}

添加DroolsManager类

新建manager包,并在manager包下新建DroolsManager.java,并将下面内容复制到该类中

package manager;

import static builder.KieModuleBuilder.*;

import entity.Rule;
import entity.RuleGroup;
import org.kie.api.KieServices;
import org.kie.api.builder.KieBuilder;
import org.kie.api.builder.KieFileSystem;
import org.kie.api.runtime.KieContainer;
import org.kie.api.runtime.StatelessKieSession;

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

public class DroolsManager {

    public static Map statelessKieSessionMap;

    private static KieContainer kieContainer;

    private static List ruleGroups;

    /**
     * 初始化
     */
    public static void init() {
        initData();
        initSystemProperties();
        KieServices kieServices = KieServices.Factory.get();
        KieFileSystem kieFileSystem = kieServices.newKieFileSystem();
        if (ruleGroups != null && ruleGroups.size() > 0){
            initAndBuildKieModuleModel(kieServices, kieFileSystem, ruleGroups);
            initAndBuildRules(kieFileSystem, ruleGroups);
            KieBuilder kieBuilder = kieServices.newKieBuilder(kieFileSystem);
            kieBuilder.buildAll();
            kieContainer = kieServices.newKieContainer(kieServices.getRepository().getDefaultReleaseId());
            statelessKieSessionMap = initStatelessKieSession(kieContainer, ruleGroups);
        }
    }

    /**
     * 添加组规则
     * @param ruleGroup
     */
    public static void addModuleAndRule(RuleGroup ruleGroup){
        if (kieContainer == null){ //规则引擎未被初始化
            init();
            return;
        }
        if (ruleGroup != null && !checkBaseModule(KieServices.Factory.get().getRepository(), ruleGroup, kieContainer.getReleaseId())){
            statelessKieSessionMap.put(ruleGroup,addBaseModuleAndRule(kieContainer, KieServices.Factory.get().getRepository(), ruleGroup));
        }
    }

    /**
     * 删除组规则
     * @param id
     * @return
     */
    public static RuleGroup removeModuleAndRule(String id){
        if (kieContainer == null) { //规则引擎未被初始化
            init();
            return getRuleGroupById(id);
        }
        RuleGroup ruleGroup = getRuleGroupById(id);
        if (ruleGroup != null && checkBaseModule(KieServices.Factory.get().getRepository(), ruleGroup, kieContainer.getReleaseId())){
            removeBaseModuleAndRule(kieContainer, KieServices.Factory.get().getRepository(), ruleGroup);
            statelessKieSessionMap.remove(ruleGroup);
        }
        return ruleGroup;
    }

    /**
     * 初始化drools时间格式化格式
     */
    private static void initSystemProperties(){
        System.setProperty("drools.dateformat","yyyy-MM-dd HH:mm:ss");
    }

    /**
     * 初始化数据
     */
    private static void initData(){
        ruleGroups = new ArrayList<>();
        //组一
        RuleGroup ruleGroup1 = new RuleGroup("1","ruleGroupOne");
        Rule rule1_1 = new Rule("1_1","ruleOne");
        rule1_1.setContent("rule \"test1_1\"\n    when\n        eval(true)\n    then\n        System.out.println(\"规则中打印日志:校验通过!1_1\");\nend");
        ruleGroup1.getRules().add(rule1_1);
        Rule rule1_2 = new Rule("1_2","ruleOne");
        rule1_2.setContent("rule \"test1_2\"\n    when\n        eval(true)\n    then\n        System.out.println(\"规则中打印日志:校验通过!1_2\");\nend");
        ruleGroup1.getRules().add(rule1_2);
        ruleGroups.add(ruleGroup1);
        //组二
        RuleGroup ruleGroup2 = new RuleGroup("2","ruleGroupTwo");
        Rule rule2_1 = new Rule("2_1","ruleTwo");
        rule2_1.setContent("rule \"test2_1\"\n    when\n        eval(true)\n    then\n        System.out.println(\"规则中打印日志:校验通过!2_1\");\nend");
        ruleGroup2.getRules().add(rule2_1);
        ruleGroups.add(ruleGroup2);
    }

    /**
     * 根据组id获取组信息
     * @param id
     * @return
     */
    private static RuleGroup getRuleGroupById(String id){
        for (Map.Entry kieSessionEntry : statelessKieSessionMap.entrySet()) {
            if (kieSessionEntry.getKey().getId().equals(id)) {
                return kieSessionEntry.getKey();
            }
        }
        return null;
    }

}

创建Main方法运行

新建一个类,并将下面内容复制到该类中

public static void main(String[] args) {
    //初始化规则引擎
    DroolsManager.init();
    execute();
    System.out.println("------------------------------");
    //移除组1
    RuleGroup ruleGroup = DroolsManager.removeModuleAndRule("1");
    execute();
    System.out.println("------------------------------");
    //添加组1
    DroolsManager.addModuleAndRule(ruleGroup);
    execute();
    System.out.println("------------------------------");
}

/**
 * 执行规则引擎
 */
private static void execute(){
    for (Map.Entry kieSessionEntry : DroolsManager.statelessKieSessionMap.entrySet()) {
        kieSessionEntry.getValue().execute(CommandFactory.newInsert(""));
    }
}

运行结果如下

SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
规则中打印日志:校验通过!1_2
规则中打印日志:校验通过!1_1
规则中打印日志:校验通过!2_1
------------------------------
规则中打印日志:校验通过!2_1
------------------------------
规则中打印日志:校验通过!2_1
规则中打印日志:校验通过!1_2
规则中打印日志:校验通过!1_1
------------------------------

Process finished with exit code 0

注意:当前只实现了将整个KieBaseModel移除,前面章节说过一个KieBaseModel下面是包含多条规则的,也就是说当我们移除组1的时候会把组1下面的两条规则都进行移除,所以移除组1后只会有一条规则被输出。

针对一组里面单一规则的增删改功能已经实现但代码目前没有整理,后续再更新吧

场景

由于规则引擎的特性when里面是判断逻辑,then里面可以写业务处理逻辑,因此规则引擎的使用场景目前个人认为有两种(仅为个人观点)

第一种是判断条件简单但存在多种可能性的且随时都会进行调整的场景,比如有5个属性ABCDE,只要满足条件(A=B || B=C || C=D || D=E || A=B=C || B=C=E....)的就返回结果,甚至有可能不同的条件搭配会生成特定的结果需要把他们的所有结果都收集起来,这时如果使用代码是不现实的

第二种数据库表逻辑查询,现在有一张表里面有10个字段,且数据很少发生变化基本只有大量查询,要求入参ABC去查这张表,表中数据类型为1的符合A=字段1即可,类型为2的要符合A=字段1且B=字段2,类型为3的要符合A=字段1且B=字段2且C=字段3....等等,SQL的查询也是不现实的,这时可以选择规则引擎,将表中的数据拼接成drlwhen的条件判断语句就是每条数据各自符合的条件,then逻辑则返回该数据的主键或数据本身,这时填充入参便能得到符合条件的所有数据

总结

Drools是一个强大规范的规则引擎,以上只是它的部分功能应用,它还支持工作流等功能,在使用前建议还是要对其有一定的了解,因为用的人比较少网上资料并不多且大多数是几年前的,否则只会寸步难行,为了配合规则引擎的使用而使用是一件令人蛋疼的事

你可能感兴趣的:(Drools入门(四)——动态规则实现)