规则引擎EasyRules_浅析

概念

Easy Rules是一个简单而强大的Java规则引擎,提供以下功能:

  • 轻量级框架和易于学习的API
  • 基于POJO的开发与注解的编程模型
  • 定义抽象的业务规则并轻松应用它们
  • 支持从简单规则创建组合规则的能力
  • 支持使用表达式语言(如MVEL和SpEL)定义规则的能力

规则对象解释

常用对象:

  • 规则参数
  • 规则引擎
  • 规则<名称、说明、优先级、事实、条件、动作>

规则常用定义:

  • 名称(name):规则命名空间中的唯一规则名称
  • 说明(description):规则的简要说明
  • 优先级(Priority):相对于其他规则的规则优先级
  • 事实(Facts):key:value结构的参数,去匹配规则时的一组已知事实
  • 条件(Condition):为了匹配该规则,在给定某些事实的情况下应满足的一组条件
  • 动作(Action):当条件满足时要执行的一组动作(可以添加/删除/修改事实)

规则对象定义

规则引擎参数

Easy Rules 引擎可以配置以下参数:

Parameter	                Type	  Required	Default
rulePriorityThreshold	    int	      no	    MaxInt
skipOnFirstAppliedRule	    boolean	  no	    false
skipOnFirstFailedRule	    boolean	  no	    false
skipOnFirstNonTriggeredRule	boolean   no	    false
  • skipOnFirstAppliedRule:告诉引擎规则被触发时跳过后面的规则。
  • skipOnFirstFailedRule:告诉引擎在规则失败时跳过后面的规则。
  • skipOnFirstNonTriggeredRule:告诉引擎一个规则不会被触发,就跳过后面的规则。
  • rulePriorityThreshold:告诉引擎如果优先级超过定义的阈值,则跳过下一个规则。版本3.3已经不支持更改,默认MaxInt。

可以使用RulesEngineParameters API指定这些参数:

RulesEngineParameters parameters = new RulesEngineParameters()
    .rulePriorityThreshold(10)
    .skipOnFirstAppliedRule(true)
    .skipOnFirstFailedRule(true)
    .skipOnFirstNonTriggeredRule(true);

RulesEngine rulesEngine = new DefaultRulesEngine(parameters);

如果要从引擎获取参数,可以使用以下代码段:

RulesEngineParameters parameters = myEngine.getParameters();

这允许您在创建引擎后重置引擎参数。

规则引擎

从版本3.1开始,Easy Rules提供了RulesEngine接口的两种实现:

  • DefaultRulesEngine:根据规则的自然顺序(默认为优先级)应用规则。
  • InferenceRulesEngine:持续对已知事实应用规则,直到不再应用规则为止。

创建一个规则引擎

要创建规则引擎,可以使用每个实现的构造函数:

RulesEngine rulesEngine = new DefaultRulesEngine();
// or
RulesEngine rulesEngine = new InferenceRulesEngine();

然后,您可以按以下方式触发注册规则:

rulesEngine.fire(rules, facts);

规则(多种定义方式)

1. 使用注解定义规则

@Rule(name = "weather rule", description = "if it rains then take an umbrella")
public class WeatherRule {
    @Condition
    public boolean itRains(@Fact("rain") boolean rain) {
        return rain;
    }
    
    @Action
    public void takeAnUmbrella() {
        System.out.println("It rains, take an umbrella!");
    }
}

2. 用函数方式定义规则

Rule weatherRule = new RuleBuilder()
        .name("weather rule")
        .description("if it rains then take an umbrella")
        .when(facts -> facts.get("rain").equals(true))
        .then(facts -> System.out.println("It rains, take an umbrella!"))
        .build();

3. 使用表达式方式

Rule weatherRule = new MVELRule()
        .name("weather rule")
        .description("if it rains then take an umbrella")
        .when("rain == true")
        .then("System.out.println(\"It rains, take an umbrella!\");");

4. 使用文件描述

weather-rule.yml

name: "weather rule"
description: "if it rains then take an umbrella"
condition: "rain == true"
actions:
  - "System.out.println(\"It rains, take an umbrella!\");"
MVELRuleFactory ruleFactory = new MVELRuleFactory(new YamlRuleDefinitionReader());
Rule weatherRule = ruleFactory.createRule(new FileReader("weather-rule.yml"));

传参事实

Facts API是一组事实的抽象,在这些事实上检查规则。在内部,Facts实例持有HashMap<String,Object>,这意味着:

  • 事实需要命名,应该有一个唯一的名称,且不能为空
  • 任何Java对象都可以充当事实

这里有一个实例定义事实:

Facts facts = new Facts();
facts.add("rain", true);

Facts 能够被注入规则条件,action 方法使用 @Fact 注解. 在下面的规则中,rain 事实被注入itRains方法的rain参数:

@Rule
class WeatherRule {

    @Condition
    public boolean itRains(@Fact("rain") boolean rain) {
        return rain;
    }

    @Action
    public void takeAnUmbrella(Facts facts) {
        System.out.println("It rains, take an umbrella!");
        // can add/remove/modify facts
    }
}

Facts类型参数 被注入已知的 facts中 (像action方法takeAnUmbrella一样).

如果缺少注入的fact, 这个引擎会抛出 RuntimeException异常.

如何使用

运行环境

Easy Rules是一个Java库, 需要运行在Java 1.7及以上。

maven依赖

<!--easy rules核心库-->
<dependency>
  <groupId>org.jeasy</groupId>
  <artifactId>easy-rules-core</artifactId>
  <version>4.1.0</version>
</dependency>

<!--规则定义文件格式,支持json,yaml等-->
<dependency>
  <groupId>org.jeasy</groupId>
  <artifactId>easy-rules-support</artifactId>
  <version>4.1.0</version>
</dependency>

<!--支持mvel规则语法库-->
<dependency>
  <groupId>org.jeasy</groupId>
  <artifactId>easy-rules-mvel</artifactId>
  <version>4.1.0</version>
</dependency>

代码示例

package com.rule.domain;

import org.jeasy.rules.annotation.Action;
import org.jeasy.rules.annotation.Condition;
import org.jeasy.rules.annotation.Fact;
import org.jeasy.rules.annotation.Rule;

public class DigitalRule {
    @Rule(name = "fizzRule", description = "能否被5整除", priority = 1)
    public static class FizzRule {
        @Condition
        public boolean isFizz(@Fact("input") int input) {
            return input % 5 == 0;
        }

        @Action
        public void printFizz(@Fact("input") int input) {
            System.out.println(input + ":能被5整除(fizz)");
        }
    }

    @Rule(name = "buzzRule", description = "能否被7整除", priority = 2)
    public static class BuzzRule {
        @Condition
        public boolean isBuzz(@Fact("input") int input) {
            return input % 7 == 0;
        }

        @Action
        public void printBuzz(@Fact("input") int input) {
            System.out.println(input + ":能被7整除(buzz)");
        }

    }

    @Rule(name = "nonFizzBuzzRule", description = "不能被5整除 或 不能被7整除", priority = 3)
    public static class NonFizzOrBuzzRule {
        @Condition
        public boolean isNotFizzOrBuzz(@Fact("input") int input) {
            return input % 5 != 0 || input % 7 != 0;
        }

        @Action
        public void printInput(@Fact("input") int input) {
            System.out.println(input + ":不能被5整除 或 不能被7整除");
        }
    }

    @Rule(name = "nonFizzBuzzRule", description = "不能被5整除 且 不能被7整除", priority = 4)
    public static class NonFizzAndBuzzRule {
        @Condition
        public boolean isNotFizzAndBuzz(@Fact("input") int input) {
            return input % 5 != 0 && input % 7 != 0;
        }

        @Action
        public void printInput(@Fact("input") int input) {
            System.out.println(input + ":不能被5整除 且 不能被7整除");
        }
    }

}

package com.rule.main;

import com.rule.domain.DigitalRule;
import org.jeasy.rules.api.*;
import org.jeasy.rules.core.DefaultRulesEngine;

public class FizzBuzzWithEasyRules {
    public static void main(String[] args) {
        RulesEngineParameters parameters = new RulesEngineParameters()
                .skipOnFirstAppliedRule(false)//告诉引擎规则被触发时跳过后面的规则。
                .skipOnFirstFailedRule(false)//告诉引擎在规则失败时跳过后面的规则。
                .skipOnFirstNonTriggeredRule(false);//告诉引擎一个规则不被触发,就跳过后面的规则。

        RulesEngine fizzBuzzEngine = new DefaultRulesEngine(parameters);

        // register rules
        Rules rules = new Rules();
        rules.register(new DigitalRule.FizzRule());
        rules.register(new DigitalRule.BuzzRule());
        rules.register(new DigitalRule.NonFizzAndBuzzRule());
        rules.register(new DigitalRule.NonFizzOrBuzzRule());
        // fire rules
        Facts facts = new Facts();
        for (int i = 0; i <= 100; i++) {
            facts.put("input", i);
            fizzBuzzEngine.fire(rules, facts);
        }
    }
}

源码解析

RuleEngine的构造就是new了一个实现,facts就是一个Map的参数结构。我们主要看register方法注册规则,以及fire方法触发规则判断的逻辑

注册rule

我们跟进Rules的register方法

private Set<Rule> rules = new TreeSet<>();

public void register(Object rule) {
    Objects.requireNonNull(rule);
    rules.add(RuleProxy.asRule(rule));
}

RuleProxy.asRule将我们传入的原始对象做了一层动态代理,然后直接添加到rules的集合当中。

很明显,后面的fire会遍历该rules。register方法的核心实现就落在了RuleProxy.asRule上了

我们跟进asRule方法

public static Rule asRule(final Object rule) {
    Rule result;
    if (rule instanceof Rule) {
        result = (Rule) rule;
    } else {
        ruleDefinitionValidator.validateRuleDefinition(rule);
        result = (Rule) Proxy.newProxyInstance(
                    Rule.class.getClassLoader(),
                    new Class[]{Rule.class, Comparable.class},
                    new RuleProxy(rule)
                );
    }
    return result;
}

asRule方法先判断了一下rule是否实现了Rule接口,如果按照接口的实现那么就不需要动态代理,直接返回。

那么没有实现rule的接口呢?

validateRuleDefinition将会进行校验判断是否符合一个rule的定义,如果不符合则抛出异常。

如果符合rule的定义,那么通过JDK的动态代理获取一个实现了Rule接口和Comparable接口的代理对象,被代理对象就是当前rule。

validateRuleDefinition

那么validateRuleDefinition是怎么判断是否符合rule的呢?

void validateRuleDefinition(final Object rule) {
    checkRuleClass(rule);
    checkConditionMethod(rule);
    checkActionMethods(rule);
    checkPriorityMethod(rule);
}

该方法分别校验了

1)是否rule类,需要被@Rule注解

2)是否有Condition条件,Condition有且只有1个。且方法必须是public修饰,返回boolean值。方法的参数必须有@Fact来修饰

3)是否有action方法,action方法数量大于等于1个。且方法必须是public修饰,返回值是void,方法的参数必须有@Facts来修饰

4)是否有priority优先级方法,priority方法可以没有,但是有的话只能有一个。且被public修饰,返回int值,同时不能有参数

fire方法触发规则逻辑

前面的规则注册,将会获取一个实现了Rule接口的rule对象。不管是自己实现的对象还是由JDK动态代理实现的对象。

跟进DefaultRulesEngine的fire方法

@Override
public void fire(Rules rules, Facts facts) {
    triggerListenersBeforeRules(rules, facts);
    doFire(rules, facts);
    triggerListenersAfterRules(rules, facts);
}

doFire前后触发了监听器,跟进doFire

doFire方法比较长,我们首先看到的是对Rules进行遍历。每个Rule先触发evaluate方法,如果evaluate返回true则进行execute方法执行。

evaluate 等同于Condition判断

execute 等同于触发action

void doFire(Rules rules, Facts facts) {
    for (Rule rule : rules) {
        final String name = rule.getName();
        final int priority = rule.getPriority();
        if (priority > parameters.getPriorityThreshold()) {
            break;
        }
        if (!shouldBeEvaluated(rule, facts)) {
            continue;
        }
        if (rule.evaluate(facts)) {
            triggerListenersAfterEvaluate(rule, facts, true);
            try {
                triggerListenersBeforeExecute(rule, facts);
                rule.execute(facts);
                triggerListenersOnSuccess(rule, facts);
                if (parameters.isSkipOnFirstAppliedRule()) {
                    break;
                }
            } catch (Exception exception) {
                triggerListenersOnFailure(rule, exception, facts);
                if (parameters.isSkipOnFirstFailedRule()) {
                    break;
                }
            }
        } else {
            triggerListenersAfterEvaluate(rule, facts, false);
            if (parameters.isSkipOnFirstNonTriggeredRule()) {
                break;
            }
        }
    }
}

evaluate条件判断

我们以代理对象为例,跟进evaluate方法。打开RuleProxy,我们看看invoke方法

@Override
public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
    String methodName = method.getName();
    switch (methodName) {
        case "getName":
            return getRuleName();
        case "getDescription":
            return getRuleDescription();
        case "getPriority":
            return getRulePriority();
        case "compareTo":
            return compareToMethod(args);
        case "evaluate":
            return evaluateMethod(args);
        case "execute":
            return executeMethod(args);
        case "equals":
            return equalsMethod(args);
        case "hashCode":
            return hashCodeMethod();
        case "toString":
            return toStringMethod();
        default:
            return null;
    }
}

可以看到,每一个被动态代理的对象将会被拦截evaluate方法。然后交付给evaluateMethod执行,我们跟进evaluateMethod方法

private Object evaluateMethod(final Object[] args) throws IllegalAccessException, InvocationTargetException {
    Facts facts = (Facts) args[0];
    Method conditionMethod = getConditionMethod();
    try {
        List<Object> actualParameters = getActualParameters(conditionMethod, facts);
        return conditionMethod.invoke(target, actualParameters.toArray());
    } catch (NoSuchFactException e) {
        return false;
    } catch (IllegalArgumentException e) {
        // ...
    }
}

getConditionMethod将会反射获取到该rule定义了@Condition的Method,然后遍历该Method的所有@Facts参数,从Facts中获取所有参数值形成一个array。

拿到Method和参数以后,直接反射触发该方法,返回一个boolean对象值

execute执行action

如果evaluate返回值为true,也就是Condition满足了。那么就会触发execute方法,action将会被调用。

实现逻辑和evaluate类似,我们跟进RuleProxy的executeMethod

private Object executeMethod(final Object[] args) throws IllegalAccessException, InvocationTargetException {
    Facts facts = (Facts) args[0];
    for (ActionMethodOrderBean actionMethodBean : getActionMethodBeans()) {
        Method actionMethod = actionMethodBean.getMethod();
        List<Object> actualParameters = getActualParameters(actionMethod, facts);
        actionMethod.invoke(target, actualParameters.toArray());
    }
    return null;
}

和evaluate类似,先是获取了rule中@Action的方法。然后获取@Fact参数值,最后返回执行,不需要返回结果。

总结

  1. 简单的条件语句,还是使用if/else快捷。
  2. 复杂的业务逻辑,例如判断不同业务规则交织时候,适合使用EasyRules,观看方便明了。
  3. 方便维护,以及后期修改。

注意点

  1. Condition条件有且只有1个。且方法必须是public修饰,返回boolean值

  2. action方法数量大于等于1个,且方法必须是public修饰,返回值是void。

  3. priority方法可以没有,但是有的话只能有一个。且被public修饰,返回int值,同时不能有参数。

  4. Easy Rules现在处于维护状态,最好的受支持版本为4.1.x,最好使用此版本。

posted @ 2021-07-11 23:13  海韵༒听心  阅读(1846)  评论(0编辑  收藏  举报