Easy Rules规则引擎(中)- 源码

315 阅读6分钟

前言

这一章介绍Easy-Rules的源码实现,主要是在使用过程中会接触到的一些源码。

github地址:github.com/j-easy/easy…
源码开源许可:MIT

常用参数

下面这些参数都在默认引擎里的doFire方法里使用,根据具体条件,判定是否执行后续规则。

  • priorityThreshold:告诉引擎如果优先级超过定义的阈值,则跳过下一个规则
  • skipOnFirstAppliedRule:当一个规则触发之后,是否跳过之后所有的规则
  • skipOnFirstFailedRule:当一个规则失败之后,是否跳过之后所有的规则
  • skipOnFirstNonTriggeredRule:当第一个规则没有触发,是否跳过之后所有的规则
        // 创建引擎参数
        RulesEngineParameters parameters = new RulesEngineParameters()
                .priorityThreshold(10)
                .skipOnFirstAppliedRule(true)
                .skipOnFirstFailedRule(true)
                .skipOnFirstNonTriggeredRule(false);

阈值的作用

如果规则优先级大于设定的阈值,则直接跳出循环,忽略后续所有规则。由于规则集合是根据优先级从小到大排序好的,所以后续优先级值更大的规则,肯定也超过了设定的阈值,可以直接忽略。

    void doFire(Rules rules, Facts facts) {
        LOGGER.debug("Rules evaluation started");
        for (Rule rule : rules) {
            final String name = rule.getName();
            final int priority = rule.getPriority();
            // 如果规则优先级大于设定的阈值,则直接跳出循环,忽略后续所有规则
            if (priority > parameters.getPriorityThreshold()) {
                LOGGER.debug("Rule priority threshold ({}) exceeded at rule '{}' with priority={}, next rules will be skipped",
                        parameters.getPriorityThreshold(), name, priority);
                break;
            }
        ......
        }
    }

规则与事实

规则和事实,一旦创建就不能修改。

  • 规则【Rule】:主要有三个参数:名称,优先级和描述,名称不能重复,否则在规则聚合类【Rules】中,会将其当成同一个规则实体,造成不可预知的异常。根据优先级进行比较大小,方便进行排序,后续会介绍使用场合。
  • 事实【Fact】:事实可以看成是一个键值对,name->Object,所以名字相同的Fact可以看成是同一个,这个与规则类似,需要重点注意。

监听器

监听器主要是在规则触发前后执行

引擎监听

  • 评估前:在评估规则集之前触发。 当此侦听器与 InferenceRulesEngine 一起使用时,此方法将在每次迭代中评估每个候选规则集之前触发。
  • 执行后:在执行规则集之后触发。 当此侦听器与 InferenceRulesEngine 一起使用时,此方法将在每次迭代中执行每个候选规则集之后触发。
    private void triggerListenersBeforeRules(Rules rule, Facts facts) {
        rulesEngineListeners.forEach(rulesEngineListener -> rulesEngineListener.beforeEvaluate(rule, facts));
    }
    
   private void triggerListenersAfterRules(Rules rule, Facts facts) {
        rulesEngineListeners.forEach(rulesEngineListener -> rulesEngineListener.afterExecute(rule, facts));
    }

触发

  • 第一个场景,在检查环节执行,检查是与fire方法平行的一个功能,用于检查规则是否可以正常通过评估;
    @Override
    public Map<Rule, Boolean> check(Rules rules, Facts facts) {
         // 触发评估前监听
        triggerListenersBeforeRules(rules, facts);
        Map<Rule, Boolean> result = doCheck(rules, facts);
         // 触发执行后监听
        triggerListenersAfterRules(rules, facts);
        return result;
    }
  • 第二个场景,在fire时执行
    @Override
    public void fire(Rules rules, Facts facts) {
        // 触发评估前监听
        triggerListenersBeforeRules(rules, facts);
        doFire(rules, facts);
        // 触发执行后监听
        triggerListenersAfterRules(rules, facts);
    }

规则监听

这些监听事件主要是在默认引擎的doFire(rules,facts)方法中触发

  • 评估前:在规则评估之前触发;
  • 评估失败:在规则评估过程中出现任何异常,都会触发;
  • 评估后:评估之后,规则是否执行,都会触发;
  • 执行前:规则执行之前触发;
  • 成功事件:规则执行未出现异常时触发;
  • 失败事件:规则执行过程中,出现任何异常都会触发;
    // 触发评估前事件
    private boolean shouldBeEvaluated(Rule rule, Facts facts) {
        return triggerListenersBeforeEvaluate(rule, facts);
    }

    void doFire(Rules rules, Facts facts) {

        LOGGER.debug("Rules evaluation started");
        for (Rule rule : rules) {
            final String name = rule.getName();
            // 触发评估前事件
            if (!shouldBeEvaluated(rule, facts)) {
                // 评估不通过,则处理下一个规则
                continue;
            }
            boolean evaluationResult = false;
            try {
                // 开始评估
                evaluationResult = rule.evaluate(facts);
            } catch (RuntimeException exception) {
                // 触发评估失败事件
                triggerListenersOnEvaluationError(rule, facts, exception);
            }
            if (evaluationResult) {
                // 触发评估后事件,true表示评估结果为是,通过评估
                triggerListenersAfterEvaluate(rule, facts, true);
                try {
                    // 触发执行前事件
                    triggerListenersBeforeExecute(rule, facts);
                    // 执行规则
                    rule.execute(facts);
                    // 触发执行成功事件
                    triggerListenersOnSuccess(rule, facts);
                } catch (Exception exception) {
                    // 触发执行失败事件
                    triggerListenersOnFailure(rule, exception, facts);
                }
            } else {
                // 触发评估后事件,false表示评估结果为否,不通过评估
                triggerListenersAfterEvaluate(rule, facts, false);
            }
        }
    }

引擎

引擎是所有操作入口,可以设定参数、注册规则集合和监听器【集合】,并触发执行。

接口

接口中的方法比较少,只是设计了几个基本功能。

  • 获取引擎参数配置,返回一个配置的复制类;
  • 获取引擎触发器集合,返回不可修改的拷贝列表;
  • 获取规则触发器集合,返回不可修改的拷贝列表;
  • 触发;
  • 检查;

实现

只有两种实现,一个是默认,一个是推理,其中推理实现是默认实现是一个装饰器模式。

默认.DefaultRulesEngine

官方说明
Rules are fired according to their natural order which is priority by default. This implementation iterates over the sorted set of rules, evaluates the condition of each rule and executes its actions if the condition evaluates to true.

规则根据其自然顺序触发,默认为优先级。 此实现迭代已排序的规则集,评估每个规则的条件,并在条件评估为真时执行其操作。是怎么排好序的呢?在Rules类中可以看到rules集合是一个TreeSet类型集合,其中Rule的比较方法如下,可以看出其是根据优先级从小到大进行排序的。执行的时候,会先执行优先级数字小的规则。

    @Override
    public int compareTo(final Rule rule) {
        if (getPriority() < rule.getPriority()) {
            return -1;
        } else if (getPriority() > rule.getPriority()) {
            return 1;
        } else {
            return getName().compareTo(rule.getName());
        }
    }

引擎中最重要的是触发方法,默认引擎的触发方法在上一节的监视器里已经做过展示并注释说明,这里就不重复了。

推理.InferenceRulesEngine

官方说明
Rules are selected based on given facts and fired according to their natural order which is priority by default. This implementation continuously selects and fires rules until no more rules are applicable.

推理引擎,规则是根据给定的事实选择的,并根据它们的自然顺序触发,默认情况下是优先级。 这个实现不断地选择和触发规则,直到没有更多的规则适用。本质上是默认引擎的一个封装,其中包含了一个默认引擎实体,具体执行和监听触发,都在默认引擎中执行。
从下面的源代码中,可以看出推理引擎是一个限定条件的无限循环处理器,只要有符合条件的规则,引擎会一直处理下去。

    @Override
    public void fire(Rules rules, Facts facts) {
        Set<Rule> selectedRules;
        do {
            // 评估符合条件的规则
            selectedRules = selectCandidates(rules, facts);
            if (!selectedRules.isEmpty()) {
                // delegate是默认引擎的实体,执行符合条件的规则集合
                delegate.fire(new Rules(selectedRules), facts);
            } else {
                LOGGER.debug("No candidate rules found for facts: {}", facts);
            }
        // 直到符合条件的候选规则为空,则跳出循环
        } while (!selectedRules.isEmpty());
    }

    // 选择符合条件的候选规则,根据优先级排序返回
    private Set<Rule> selectCandidates(Rules rules, Facts facts) {
        // 此处也是根据优先级进行排序的规则集合
        Set<Rule> candidates = new TreeSet<>();
        for (Rule rule : rules) {
            // 进行规则评估
            if (rule.evaluate(facts)) {
                candidates.add(rule);
            }
        }
        return candidates;
    }
    

两者的区别

推理引擎一旦触发,只要有满足条件的规则,会无限循环下去;而默认引擎只会触发一次。从这个特性可以看出,默认引擎适合定时任务,到指定时间执行一次即可;而推理引擎则比较适合常驻业务,对业务请求进行持续、及时的处理。