Drools开发实战

473 阅读6分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第9天,点击查看活动详情

1、前言

在上一篇的分享中(juejin.cn/post/710504…)为大家介绍了Drools的一些基本知识,但在实际开发中,还是做了一些工具类进行了二次封装,同时也将规则存储到数据库中,而不是文件里。本篇就为大家介绍下。

2、封装Rules工具类

这里主要是将规则的执行、创建会话、创建知识库等常用方法进行了二次封装,方便后续傻瓜化开发。有了工具类,以后只需要关心规则的编写即可。

/**
 * 规则和规则模板的为多对一关系,规则生成的规则及默认信息:
 *  1)不指定参数,生成规则
 *      规则名:规则模板名
 *      规则容器名:规则名_KB
 *      规则会话名:规则名_KS
 *  2)指定参数,生成规则
 *      规则名:规则模板名_规则参数名
 *      规则容器名:规则名_KB
 *      规则会话名:规则名_KS
 *
 * @ClassName:   RuleUtil
 * @Description: 规则引擎工具类
 */
public class RuleUtil {

    public static KieContainer buildContainer(ReleaseId releaseId){
        KieServices kieServices = KieServices.Factory.get();
        return kieServices.newKieContainer(releaseId);
    }

    public static void buildKie(KieFileSystem kieFileSystem){
        KieServices kieServices = KieServices.Factory.get();
        KieBuilder kieBuilder = kieServices.newKieBuilder(kieFileSystem);
        kieBuilder.buildAll();
    }

    public static KieFileSystem newKieFileSystem(){
        KieServices kieServices = KieServices.Factory.get();
        return kieServices.newKieFileSystem();
    }

    public static KieModuleModel buildKieModuleModel(String kieBaseName, String pkgName, String kieSessionName){
        KieServices kieServices = KieServices.Factory.get();
        KieModuleModel kieModuleModel = kieServices.newKieModuleModel();
        KieBaseModel kieBaseModel = kieModuleModel.newKieBaseModel(kieBaseName).addPackage(pkgName);
        kieBaseModel.newKieSessionModel(kieSessionName);
        return kieModuleModel;
    }

    /**
     * 有状态会话执行
     * @param kieSession
     * @param factList
     */
    public static void execEval(KieSession kieSession, Object ...factList){
        for (Object fact : factList) {
            kieSession.insert(fact);
        }
        kieSession.fireAllRules();
        for (FactHandle factHandle : kieSession.getFactHandles()) {
            kieSession.delete(factHandle);
        }
        kieSession.dispose();
    }

    /**
     * @description 启动定时器规则
     * @param kieSession 规则会话
     * @param factList 事实列表
     * @return void
     * @modified
     * @throws  
     */
    public static void fireUntilHalt(KieSession kieSession, Object ...factList){
        for (Object fact : factList) {
            kieSession.insert(fact);
        }
        kieSession.fireUntilHalt();
    }

    /**
     * @description 停止监视器规则,并销毁会话资源
     * @param kieSession 规则回话
     * @return void
     * @modified
     * @throws  
     */
    public static void haltAndDispose(KieSession kieSession){
        kieSession.halt();
        Collection<FactHandle> factHandles = kieSession.getFactHandles();
        for (FactHandle factHandle : factHandles) {
            kieSession.delete(factHandle);
        }
        kieSession.dispose();
    }

    /**
     * 根据DRT文件构建会话
     * @param drtClasspath
     * @param templateName
     * @param paramName
     * @param packageName
     * @param paramCollection
     * @return
     * @throws IOException
     */
    public static KieSession buildSessionByDrtAndParam(String drtClasspath, String templateName, String paramName, String packageName, Collection<Map> paramCollection) throws IOException {
        KieContainer kieContainer = buildKieContainerByDrtFile(drtClasspath, templateName, paramName, packageName, paramCollection);
        return kieContainer.newKieSession(buildKieSessionName(templateName, paramName));
    }

    /**
     * 根据DRT文件创建容器
     * 将根据参数创建默认的DRL路径、知识库名、会话名
     * @param drtClasspath
     * @param templateName
     * @param paramName
     * @param templatePkgName
     * @param paramCollection
     * @return
     * @throws IOException
     */
    public static KieContainer buildKieContainerByDrtFile(String drtClasspath, String templateName, String paramName, String templatePkgName, Collection<Map> paramCollection) throws IOException {
        String packageName = StringUtils.isEmpty(paramName) ? templatePkgName : (templatePkgName + DOT + paramName) ;
        String drlPath = buildDrlPath(packageName, templateName, paramName);
        String drl = buildDrlStringByDrtFile(drtClasspath, paramCollection);
        String kieBaseName = buildKieBaseName(templateName, paramName);
        String kieSessionName = buildKieSessionName(templateName, paramName);

        return buildKieContainer(packageName, drlPath, drl, kieBaseName, kieSessionName);
    }


    /**
     * 使用
     * @param packageName
     * @param drlPath
     * @param drl
     * @param kieBaseName
     * @param kieSessionName
     * @return
     */
    public static KieContainer buildKieContainer(String packageName, String drlPath, String drl
            , String kieBaseName, String kieSessionName) {
        return buildKieContainer(packageName, drlPath, drl, kieBaseName, kieSessionName, DEFAULT_RELEASEID_ID);
    }

    /**
     * 根据DRL创建容器,使用指定ReleaseId
     * @param packageName
     * @param drlPath
     * @param drl
     * @param kieBaseName
     * @param kieSessionName
     * @param releaseId 发布ID,不指定使用默认的
     * @return
     */
    public static KieContainer buildKieContainer(String packageName, String drlPath, String drl
            , String kieBaseName, String kieSessionName, ReleaseId releaseId) {
        KieServices kieServices = KieServices.Factory.get();
        KieModuleModel kieModuleModel = kieServices.newKieModuleModel();
        KieBaseModel kieBaseModel = kieModuleModel.newKieBaseModel(kieBaseName).addPackage(packageName);
        KieSessionModel kieSessionModel = kieBaseModel.newKieSessionModel(kieSessionName);

        KieFileSystem fileSystem = kieServices.newKieFileSystem();
        fileSystem.write(drlPath, drl.getBytes(StandardCharsets.UTF_8));
        String xml = kieModuleModel.toXML();
        fileSystem.writeKModuleXML(xml);
        fileSystem.generateAndWritePomXML(releaseId != null ? releaseId : DEFAULT_RELEASEID_ID);

        KieBuilder kb = kieServices.newKieBuilder(fileSystem);
        kb.buildAll();
        if (kb.getResults().hasMessages(Message.Level.ERROR)) {
            throw new RuntimeException("知识库构建发生错误:" + kb.getResults().toString());
        }

        return kieServices.newKieContainer(releaseId != null ? releaseId : DEFAULT_RELEASEID_ID);

    }

    /**
     * 生成知识会话名
     * @param templateName 模板名
     * @param paramName 参数名
     * @return
     */
    public static String buildKieSessionName(String templateName, String paramName) {
        return templateName + (StringUtils.isEmpty(paramName) ? "" : (UNDERLINE + paramName)) + UNDERLINE + KIE_SESSION_SUFIX;
    }

    /**
     * 生成知识库名
     * @param templateName 模板名
     * @param paramName 参数名
     * @return
     */
    public static String buildKieBaseName(String templateName, String paramName) {
        return templateName + (StringUtils.isEmpty(paramName) ? "" : (UNDERLINE + paramName)) + UNDERLINE + KIE_BASE_SUFIX;
    }


    /**
     * 根据DRT输入流生成DRL字符串
     * @param paramCollection
     * @param drtInputStream
     * @return
     */
    public static String buildDrlStringByDrtInputStream(Collection<?> paramCollection, InputStream drtInputStream){
        ObjectDataCompiler compiler = new ObjectDataCompiler();
        return compiler.compile(paramCollection, drtInputStream);
    }

    /**
     * 根据DRT字符串生成DRL
     * @param drtStr
     * @param paramCollection
     * @return
     */
    public static String buildDrlStringByDrtString(String drtStr, Collection<?> paramCollection){
        ObjectDataCompiler compiler = new ObjectDataCompiler();
        return compiler.compile(paramCollection, new ByteArrayInputStream(drtStr.getBytes(StandardCharsets.UTF_8)));
    }

    /**
     * 根据DRT文件生成DRL
     * @param drtClasspath 文件类路径
     * @param paramCollection 参数
     * @return
     * @throws IOException
     */
    public static String buildDrlStringByDrtFile(String drtClasspath, Collection<Map> paramCollection) throws IOException {
        ObjectDataCompiler compiler = new ObjectDataCompiler();
        InputStream inputStream = ResourceFactory.newClassPathResource(drtClasspath, RuleUtil.class).getInputStream();
        return compiler.compile(paramCollection, inputStream);
    }

    /**
     * 生成drl路径
     * @param packageName 包名
     * @param templateName 模板名
     * @param paramName 参数名(一个模板,多组参数,使用参数名区分)
     * @return
     */
    public static String buildDrlPath(String packageName, String templateName, String paramName) {
        String dir = RESOURCE_ROOT_PATH + FORWARD_SLASH + packageName.replace(DOT, FORWARD_SLASH) + FORWARD_SLASH;
        String fileName = templateName
                + (StringUtils.isEmpty(paramName) ? "" : (UNDERLINE + paramName)) + DRL_FILE_SURFIX;
        return  dir + fileName;
    }

    /**
     * 根据DRT文件获取会话
     * @param drtClasspath
     * @return
     */
    public static KieSession getKieSessionByDrtFile(String drtClasspath, Collection<Map> paramCollection) throws IOException {
        String drl = buildDrlStringByDrtFile(drtClasspath, paramCollection);
        KieHelper kieHelper = new KieHelper();
        kieHelper.addContent(drl, ResourceType.DRL);
        return kieHelper.build().newKieSession();
    }

    /**
     * 根据DRL文件获取会话
     * @param drlClasspath
     * @return
     */
    public static KieSession getKieSessionByDrlFile(String drlClasspath) {
        Resource resource = ResourceFactory.newClassPathResource(drlClasspath, StandardCharsets.UTF_8.toString());
        KieHelper kieHelper = new KieHelper();
        kieHelper.addResource(resource, ResourceType.DRL);
        return kieHelper.build().newKieSession();
    }


    /**
     * 获取规则名
     * @param templateName 模板名
     * @param templateParamName 参数名
     * @return
     */
    public static String buildRuleName(String templateName, String templateParamName){
         return templateName + (StringUtils.isEmpty(templateParamName) ? "" : UNDERLINE + templateParamName);
    }

    /**
     * 获取artifactid
     * @param templateName
     * @param templateParamName
     * @return
     */
    public static String buildDefaultArtifactId(String templateName, String templateParamName){
        return templateName + (StringUtils.isEmpty(templateParamName) ? "" : UNDERLINE + templateParamName);
    }

    /**
     * 根据规则名获取规则库名
     * @param ruleName
     * @return
     */
    public static String buildBaseNameByRuleName(String ruleName){
        return ruleName + UNDERLINE + KIE_BASE_SUFIX;
    }

    /**
     * 根据规则名获取规会话名
     * @param ruleName
     * @return
     */
    public static String buildSessionNameByRuleName(String ruleName){
        return ruleName + UNDERLINE + KIE_SESSION_SUFIX;
    }

    /**
     * @Title:       getDrl
     * @Description: 获取规则引擎模板
     * @param:       fileName
     * @param:       sc
     * @param:       className
     * @return:      String(返回类型)
     * @throws
     */
    public static String getDrl(String fileName, ServletContext sc, String className){
        //根据文件名获取(本应用包中的)规则文件
        File file = FileUtil.getFile(fileName, sc);
        //根据包含全路径的类名获取(加载jar包中的)规则文件
        File classPathFile = FileUtil.getFile(fileName, className);
        if(null != classPathFile){
            file = classPathFile;
        }
        //根据绝对路径获取规则文件
        File absolutePathFile = FileUtil.getFile(fileName);
        if(null != absolutePathFile){
            file = absolutePathFile;
        }
        if(null != file){
            return FileUtil.getStringByFile(file);
        }
        return "";
    }

}

3、项目启动时加载所有Rules至KieContainer内

因为所有的规则内容存储到了数据库中,所以我们需要在项目启动的时候,将所有的Rules加载到规则容器内。这里我是使用的@PostConstruct注解。

/**
 * @ClassName:   RuleBuilder
 * @Description: 规则引擎创建类
 */
@Component
@Slf4j
public class RuleBuilder {

    @Autowired
    private RuleDao ruleDao;
    @Autowired
    private RuleTemplateDao ruleTemplateDao;

    private static KieServices kieService = KieServices.Factory.get();

    private static volatile Map<String, KieContainer> kieContainerMap = new ConcurrentHashMap<>();

    @PostConstruct
    public void initKieContainer(){
        loadAllKieContainer();
    }

    /**
     * 加载全部配置规则到容器
     */
    public synchronized void loadAllKieContainer() throws Exception {
        List<Rule> ruleList = ruleDao.findAll();
        for (Rule rule : ruleList) {
            String ruleName = RuleUtil.buildRuleName(rule.getRulename(), rule.getTemplateparamname());
            try{
                if(rule.getTemplatename() == null){
                    log.error("规则【{}】加载失败,未查询到规则模板。", ruleName);
                    continue;
                }
                Collection<Map> paramCollection = getParamCollection(rule);
                Rule updateRule = loadKieContainer(rule, paramCollection);
                ruleDao.update(updateRule);
            }catch(Exception e){
                log.error("规则【{}】加载发生异常。", ruleName, e);
            }
        }
    }

    private Collection<Map> getParamCollection(Rule rule) {
        Collection<Map> paramCollection = new ArrayList<>();
        Map<String, Object> param = new HashMap<>();
        param.put("ruleName", RuleUtil.buildRuleName(rule.getTemplatename().getTemplatename(), rule.getTemplateparamname()));
        paramCollection.add(param);
        return paramCollection;
    }

    /**
     * @description 根据规则模板名(必须)、规则模板参数名,使用默认配置构造规则库(详见RuleUtil),目前不支持自定义
     * @param rule : 
     * @return org.cpms.common.rules.entity.Rule
     * @modified
     * @throws  
     */
    public Rule loadKieContainer(Rule rule, Collection<?> paramCollection) {
        // 容器参数
        String templateName = rule.getTemplatename().getTemplatename();
        String templateParamName = rule.getTemplateparamname();
        String templatePkgName = rule.getTemplatename().getTemplatepkgname();
        String drtString = rule.getTemplatename().getDrt();
        // 生成容器
        String ruleName = RuleUtil.buildRuleName(templateName, templateParamName);
        String packageName = StringUtils.isEmpty(templateParamName) ? templatePkgName : (templatePkgName + RuleUtil.DOT + templateParamName) ;
        String drlPath = RuleUtil.buildDrlPath(packageName, templateName, templateParamName);
        String drl = RuleUtil.buildDrlStringByDrtString(drtString, paramCollection);
        String kieBaseName = RuleUtil.buildKieBaseName(templateName, templateParamName);
        String kieSessionName = RuleUtil.buildKieSessionName(templateName, templateParamName);
        String artifactId = RuleUtil.buildDefaultArtifactId(templateName, templateParamName);

        ReleaseId releaseId = KieServices.Factory.get().newReleaseId(RuleUtil.DEFAULT_GROUP_ID, artifactId, RuleUtil.DEFAULT_VERSION);
        KieContainer kieContainer = RuleUtil.buildKieContainer(packageName, drlPath, drl
                , kieBaseName, kieSessionName, releaseId);
        //存在则覆盖
        kieContainerMap.put(ruleName, kieContainer);
        // 规则信息保存
        rule.setGroupid(RuleUtil.DEFAULT_GROUP_ID);
        rule.setArtifactid(artifactId);
        rule.setVersion(RuleUtil.DEFAULT_VERSION);

        rule.setRulename(ruleName);
        rule.setDrl(drl);
        rule.setKbasename(kieBaseName);
        rule.setKsessionname(kieSessionName);
        rule.setPkgname(packageName);
        rule.setUpdatetime(new Date());
        return rule;
    }
    /**
     * @description 根据规则模板名称、模板参数名称生成或更新规则,并加载规则容器
     * @param templateName : 
     * @param templateParamName :
     * @return void
     * @modified
     * @throws  
     */
    public void loadKieContainer(String templateName, String templateParamName) throws Exception {
        String ruleName = RuleUtil.buildRuleName(templateName, templateParamName);
        Rule rule = ruleDao.findByRuleName(ruleName);
        boolean updateRule = true;
        if(rule == null){
            rule = new Rule();
            updateRule = false;
        }
        rule.setTemplateparamname(templateParamName);
        RuleTemplate ruleTemplate = ruleTemplateDao.findByTemplateName(templateName);
        String ruleTemplateStr = ruleTemplate.toString();
        if(ruleTemplate == null){
            String errMsg = MessageFormat.format("加载规则容器失败,未查询到规则模板【{0}】。"
                    , templateName);
            log.error(errMsg);
            throw new Exception(errMsg);
        }
        String drt = ruleTemplate.getDrt();
        rule.setTemplatename(ruleTemplate);
        rule = loadKieContainer(rule, getParamCollection(rule));
        if(updateRule){
            ruleDao.update(rule);
        }else{
            ruleDao.save(rule);
        }
        log.info("规则容器【{}】加载成功。", rule.getRulename());
    }

    public void removeContainerAndRule(String ruleName) throws Exception {
        removeRuleByName(ruleName);
        removeKieContainerByRuleName(ruleName);
    }

    /**
     * @description 移除容器
     * @param ruleName 规则名称
     * @return boolean
     * @modified
     * @throws  
     */
    public static void removeKieContainerByRuleName(String ruleName){
        KieContainer disposedKieContainer = kieContainerMap.remove(ruleName);
        if(disposedKieContainer != null){
            disposedKieContainer.dispose();
            KieServices.Factory.get().getRepository().removeKieModule(disposedKieContainer.getReleaseId());
        }
    }
    /**
     * @description 移除规则条目
     * @param ruleName 规则名称
     * @return void
     * @modified
     * @throws
     */
    public void removeRuleByName(String ruleName) throws Exception {
        Rule rule = ruleDao.findByRuleName(ruleName);
        if(rule != null){
            ruleDao.delete(rule);
        }
    }

    public static synchronized KieSession newKieSession(String ruleName){
        KieContainer kieContainer = getKieContainer(ruleName);
        if(kieContainer == null){
            log.info("未查询到规则名为【{}】的规则容器。", ruleName);
            return null;
        }
        return kieContainer.newKieSession(RuleUtil.buildSessionNameByRuleName(ruleName));
    }

    public static KieContainer getKieContainer(String ruleName) {
        return kieContainerMap.get(ruleName);
    }

}

经过上述的二次封装,大大的方便了其他组员的开发,只需专注规则的编写,同时也方便规则引擎的统一管理与升级。

好了、本期就先介绍到这里,有什么需要交流的,大家可以随时私信我。😊