zoukankan      html  css  js  c++  java
  • 规则引擎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,最好使用此版本。

  • 相关阅读:
    Python的数据类型--数字--字符串
    python基本--数据类型
    系统分区 ,硬盘格式化,
    linux 用户创建,权限,分组
    协程
    进程
    线程与进程--线程三把锁
    线程
    socket网络编程-字典
    socket网络编程
  • 原文地址:https://www.cnblogs.com/mumuyinxin/p/15000115.html
Copyright © 2011-2022 走看看