欢迎访问悦橙教程(wld5.com),关注java教程。悦橙教程  java问答|  每日更新
页面导航 : > > 文章正文

Java规则引擎easy-rules详细介绍,

来源: javaer 分享于  点击 23714 次 点评:141

Java规则引擎easy-rules详细介绍,


目录
  • 简介
  • 开始使用
    • 引入依赖
    • 定义规则
      • 使用注解定义规则
      • 使用RuleBuilder定义规则
      • 组合规则
      • 规则优先级
      • Rules API
    • 定义事实
      • 定义规则引擎
        • 创建规则引擎
        • 规则引擎参数
      • 定义规则监听器
        • 定义规则引擎监听器
          • 表达式语言(EL)支持
            • EL提供者注意事项
            • 通过编程的方式定义规则
            • 通过规则描述文件定义规则
            • 规则定义中的错误处理
        • 实际栗子

          最近在思考一个基于规则进行挑选的技术重构,想通过规则引擎进行实现,借着这个机会正好可以详细了解一下规则引擎。本篇文章将会详细介绍规则引擎easy-rules的使用。项目地址:https://github.com/j-easy/easy-rules

          简介

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

          • 轻量级框架和易于学习的API
          • 基于POJO的开发
          • 支持从原始规则创建组合规则
          • 支持通过表达式(如MVEL,SPEL和JEXL)定义规则

          开始使用

          引入依赖

          <dependency>
              <groupId>org.jeasy</groupId>
              <artifactId>easy-rules-core</artifactId>
              <version>4.1.0</version>
          </dependency>

          上面只引入了core模块依赖,如需要其它模块内容,再引入对应依赖即可。

          定义规则

          介绍

          大多数业务规则可以用以下定义表示:

          • name:规则命名空间中的唯一规则名称
          • description:规则的简要描述
          • priority:规则的优先级
          • facts:触发规则时的一组已知事实
          • conditions:在给定一些事实的情况下,为了应用该规则,需要满足的一组条件
          • actions:满足条件时要执行的一组操作(可能会添加/删除/修改事实)

          Easy Rules为定义业务规则的每个关键点提供了抽象。Easy Rules中的规则由Rule接口表示:

          public interface Rule extends Comparable<Rule> {
          
              /**
              * 此方法封装了规则的条件。
              * @return 如果根据提供的事实可以应用规则,则为true,否则为false
              */
              boolean evaluate(Facts facts);
          
              /**
              * 此方法封装了规则的操作。
              * @throws 如果在执行操作期间发生错误,则抛出异常
              */
              void execute(Facts facts) throws Exception;
          
              //Getters and setters for rule name, description and priority omitted.
          
          }
          

          evaluate()方法封装了必须为true才能触发规则的条件。execute()方法封装了在满足规则条件时应该执行的操作。条件和操作由Condition和Action接口表示。
          规则可以用两种不同的方式定义:

          • 通过在POJO上添加注解来声明
          • 通过RuleBuilder API编程

          这些是定义规则的最常用方法,但是如果需要,您也可以实现Rule接口或扩展BasicRule类。

          使用注解定义规则

          Easy Rules提供了@Rule注解,可以将POJO转换为规则。

          @Rule(name = "my rule", description = "my rule description", priority = 1)
          public class MyRule {
          
              @Condition
              public boolean when(@Fact("fact") fact) {
                  // 规则条件
                  return true;
              }
          
              @Action(order = 1)
              public void then(Facts facts) throws Exception {
                  // 规则为true时的操作1
              }
          
              @Action(order = 2)
              public void finally() throws Exception {
                  // 规则为true时的操作2
              }
          }

          @Condition注解用来标记评估规则条件的方法,这个方法必须是public,可以有一个或多个带@Fact注解的参数,并返回一个boolean类型。只有一个方法可以用@Condition注解标记。
          @Action注解用来标记执行操作的方法,规则可以有多个操作。可以使用order属性以指定的顺序执行操作。

          使用RuleBuilder定义规则

          RuleBuilder允许你用流式API定义规则。

          Rule rule = new RuleBuilder()
                          .name("myRule")
                          .description("myRuleDescription")
                          .priority(3)
                          .when(condition)
                          .then(action1)
                          .then(action2)
                          .build();

          在本例中,condition是Condition接口的实例,action1和action2是Action接口的实例。

          组合规则

          Easy Rules允许从原始规则创建复杂的规则。一个CompositeRule由一组规则组成。组合规则是一个抽象概念,因为组合规则可以以不同的方式触发。Easy Rules提供了3种CompositeRule的实现。

          • UnitRuleGroup:单元规则组是作为一个单元使用的组合规则,要么应用所有规则,要么不应用任何规则。
          • ActivationRuleGroup:激活规则组触发第一个适用规则并忽略组中的其他规则。规则首先按照其在组中的自然顺序(默认情况下优先级)进行排序。
          • ConditionalRuleGroup:条件规则组将具有最高优先级的规则作为条件,如果具有最高优先级的规则的计算结果为true,那么将触发其余的规则。

          组合规则可以从原始规则创建并像常规规则一样注册。

          // 从两个原始规则创建组合规则
          UnitRuleGroup myUnitRuleGroup =
              new UnitRuleGroup("myUnitRuleGroup", "unit of myRule1 and myRule2");
          myUnitRuleGroup.addRule(myRule1);
          myUnitRuleGroup.addRule(myRule2);
          
          // 像常规规则一样注册组合规则
          Rules rules = new Rules();
          rules.register(myUnitRuleGroup);
          
          RulesEngine rulesEngine = new DefaultRulesEngine();
          rulesEngine.fire(rules, someFacts);

          规则优先级

          Easy Rules中的每个规则都有一个优先级。这表示触发注册规则的默认顺序。默认情况下,值越低优先级越高。要覆盖此行为,您应该重写compareTo()方法以提供自定义优先级策略。

          • 如果是继承BasicRule,可以在构造方法中指定优先级,或者重写getPriority()方法。
          • 如果是使用POJO定义规则,可以通过@Rule注解的priority属性指定优先级,或者使用@Priority注解标记一个方法。这个方法必须是public,无参却返回类型为Integer。
          • 如果使用RuleBuilder定义规则,可以使用RuleBuilder#priority()方法指定优先级。

          Rules API

          Easy rules中的一组规则由rules API表示。它的使用方法如下:

          Rules rules = new Rules();
          rules.register(myRule1);
          rules.register(myRule2);

          Rules表示已注册规则的命名空间,因此,在同一命名空间下,每一个已经注册的规则必须有唯一的名称。

          Rules是通过Rule#compareTo()方法进行比较的,因此,Rule的实现应该正确的实现compareTo()方法来确保单一空间下拥有唯一的规则名称。

          定义事实

          Easy Rules中的一个事实是由Fact表示的:

          public class Fact<T> {
             private final String name;
             private final T value;
          }

          一个事实有一个名称和一个值,两者都不能为null。另一方面,Facts API 表示一组事实并充当事实的命名空间。这意味着,在一个Facts实例中,事实必须有唯一的名称。
          下面是一个如何定义事实的例子:

          Fact<String> fact = new Fact("foo", "bar");
          Facts facts = new Facts();
          facts.add(fact);

          你也可以使用一个更短的版本,用put方法创建命名的事实,如下所示:

          Facts facts = new Facts();
          facts.put("foo", "bar");

          可以使用@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的参数将被注入所有已知的事实。
          注意:

          • 如果条件方法中缺少注入的事实,引擎将记录一个警告,并认为条件被计算为false。
          • 如果动作方法中缺少注入的事实,则不会执行该动作,并且抛出org.jeasy.rules.core.NoSuchFactException异常。

          定义规则引擎

          Easy Rules提供了RulesEngine接口的两种实现:

          • DefaultRulesEngine:根据规则的自然顺序(默认为优先级)应用规则。
          • InferenceRulesEngine:在已知的事实上不断地应用规则,直到没有更多的规则可用。

          创建规则引擎

          可以使用构造方法创建规则引擎。

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

          可以按如下方式触发已注册的规则。

          rulesEngine.fire(rules, facts);

          规则引擎参数

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

          参数类型默认值
          rulePriorityThresholdintMaxInt
          skipOnFirstAppliedRulebooleanfalse
          rulePriorityThresholdintfalse
          skipOnFirstFailedRulebooleanfalse
          skipOnFirstNonTriggeredRulebooleanfalse
          • skipOnFirstAppliedRule:当一个规则成功应用时,跳过余下的规则。
          • skipOnFirstFailedRule:当一个规则失败时,跳过余下的规则。
          • skipOnFirstNonTriggeredRule:当一个规则未触发时,跳过余下的规则。
          • rulePriorityThreshold:当优先级超过指定的阈值时,跳过余下的规则。

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

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

          如果你想从你的引擎中获取参数,你可以使用以下代码段:

          RulesEngineParameters parameters = myEngine.getParameters();

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

          定义规则监听器

          可以通过RuleListener API来监听规则执行事件:

          public interface RuleListener {
          
              /**
               * 在评估规则之前触发。
               *
               * @param rule 正在被评估的规则
               * @param facts 评估规则之前的已知事实
               * @return 如果规则应该评估,则返回true,否则返回false
               */
              default boolean beforeEvaluate(Rule rule, Facts facts) {
                  return true;
              }
          
              /**
               * 在评估规则之后触发
               *
               * @param rule 评估之后的规则
               * @param facts 评估规则之后的已知事实
               * @param evaluationResult 评估结果
               */
              default void afterEvaluate(Rule rule, Facts facts, boolean evaluationResult) { }
          
              /**
               * 运行时异常导致条件评估错误时触发
               *
               * @param rule 评估之后的规则
               * @param facts 评估时的已知事实
               * @param exception 条件评估时发生的异常
               */
              default void onEvaluationError(Rule rule, Facts facts, Exception exception) { }
          
              /**
               * 在规则操作执行之前触发。
               *
               * @param rule 当前的规则
               * @param facts 执行规则操作时的已知事实
               */
              default void beforeExecute(Rule rule, Facts facts) { }
          
              /**
               * 在规则操作成功执行之后触发
               *
               * @param rule t当前的规则
               * @param facts 执行规则操作时的已知事实
               */
              default void onSuccess(Rule rule, Facts facts) { }
          
              /**
               * 在规则操作执行失败时触发
               *
               * @param rule 当前的规则
               * @param facts 执行规则操作时的已知事实
               * @param exception 执行规则操作时发生的异常
               */
              default void onFailure(Rule rule, Facts facts, Exception exception) { }
          
          }

          可以实现这个接口来提供自定义行为,以便在每个规则之前/之后执行。要注册监听器,请使用以下代码段:

          DefaultRulesEngine rulesEngine = new DefaultRulesEngine();
          rulesEngine.registerRuleListener(myRuleListener);

          可以注册任意数量的侦听器,它们将按照注册顺序执行。
          注意:当使用组合规则时,监听器是围绕组合规则调用的。

          定义规则引擎监听器

          可以通过RulesEngineListener API来监听规则引擎的执行事件:

          public interface RulesEngineListener {
          
              /**
               * 在执行规则集之前触发
               *
               * @param rules 要触发的规则集
               * @param facts 触发规则前的事实
               */
              default void beforeEvaluate(Rules rules, Facts facts) { }
          
              /**
               * 在执行规则集之后触发
               *
               * @param rules 要触发的规则集
               * @param facts 触发规则前的事实
               */
              default void afterExecute(Rules rules, Facts facts) { }
          }

          RulesEngineListener允许我们在触发整个规则集之前/之后提供自定义行为。可以使用如下方式注册监听器。

          DefaultRulesEngine rulesEngine = new DefaultRulesEngine();
          rulesEngine.registerRulesEngineListener(myRulesEngineListener);

          可以注册任意数量的监听器,它们将按照注册顺序执行。

          表达式语言(EL)支持

          Easy Rules支持用MVEL、SpEL和JEXL定义规则。

          EL提供者注意事项

          EL提供者在行为上有一些区别。例如,当一个事实在条件中缺失时,MVEL抛出一个异常,而SpEL将忽略它并返回false。因此,在选择Easy Rules使用哪个EL之前,你应该了解这些差异。

          通过编程的方式定义规则

          条件、动作和规则分别由MVELCondition/SpELCondition/JexlCondition、MVELAction/SpELAction/JexlAction和MVELRule/SpELRule/JexlRule类表示。下面是一个使用MVEL定义规则的例子:

          Rule ageRule = new MVELRule()
                  .name("age rule")
                  .description("Check if person's age is > 18 and marks the person as adult")
                  .priority(1)
                  .when("person.age > 18")
                  .then("person.setAdult(true);");

          通过规则描述文件定义规则

          可以使用规则描述文件定义规则,使用MVELRuleFactory/SpELRuleFactory/JexlRuleFactory来从描述符文件创建规则。下面是一个在alcohol-rule.yml中以YAML格式定义的MVEL规则示例:

          name: "alcohol rule"
          description: "children are not allowed to buy alcohol"
          priority: 2
          condition: "person.isAdult() == false"
          actions:
            - "System.out.println("Shop: Sorry, you are not allowed to buy alcohol");"
          MVELRuleFactory ruleFactory = new MVELRuleFactory(new YamlRuleDefinitionReader());
          MVELRule alcoholRule = ruleFactory.createRule(new FileReader("alcohol-rule.yml"));

          还可以使用一个文件创建多个规则。

          ---
          name: adult rule
          description: when age is greater than 18, then mark as adult
          priority: 1
          condition: "person.age > 18"
          actions:
            - "person.setAdult(true);"
          ---
          name: weather rule
          description: when it rains, then take an umbrella
          priority: 2
          condition: "rain == true"
          actions:
            - "System.out.println("It rains, take an umbrella!");"

          可以使用如下方式将这些规则加载到rules对象中。

          MVELRuleFactory ruleFactory = new MVELRuleFactory(new YamlRuleDefinitionReader());
          Rules rules = ruleFactory.createRules(new FileReader("rules.yml"));

          Easy Rules还支持从JSON描述符加载规则。具体参考文档,这里不做展开。

          规则定义中的错误处理

          关于条件中不正确表达式的引擎行为
          对于条件求值过程中可能发生的任何运行时异常(丢失事实、表达式中输入错误等),引擎将记录一个警告,并认为条件求值为false。可以使用RuleListener#onEvaluationError来监听评估错误。

          关于操作中不正确表达式的引擎行为
          对于任何在执行操作时可能发生的运行时异常(丢失事实、表达式中输入错误等),该操作将不会执行,引擎将记录一个错误。可以使用RuleListener#onFailure来监听操作执行异常。当一个规则失败时,引擎将移动到下一个规则,除非设置了skipOnFirstFailedRule参数。

          实际栗子

          本栗子使用Easy Rules实现FizzBuzz应用程序。FizzBuzz是一个简单的应用程序,需要从1数到100,并且:

          • 如果数字是5的倍数,则打印“fizz”
          • 如果数字是7的倍数,请打印“buzz”
          • 如果数字是5和7的倍数,请打印“fizzbuzz”
          • 否则打印数字本身
          public class FizzBuzz {
            public static void main(String[] args) {
              for(int i = 1; i <= 100; i++) {
                if (((i % 5) == 0) && ((i % 7) == 0))
                  System.out.print("fizzbuzz");
                else if ((i % 5) == 0) System.out.print("fizz");
                else if ((i % 7) == 0) System.out.print("buzz");
                else System.out.print(i);
                System.out.println();
              }
              System.out.println();
            }
          }

          我们将为每个需求编写一条规则:

          @Rule
          public class FizzRule {
          
              @Condition
              public boolean isFizz(@Fact("number") Integer number) {
                  return number % 5 == 0;
              }
          
              @Action
              public void printFizz() {
                  System.out.print("fizz");
              }
          
              @Priority
              public int getPriority() {
                  return 1;
              }
          }
          
          @Rule
          public class BuzzRule {
          
              @Condition
              public boolean isBuzz(@Fact("number") Integer number) {
                  return number % 7 == 0;
              }
          
              @Action
              public void printBuzz() {
                  System.out.print("buzz");
              }
          
              @Priority
              public int getPriority() {
                  return 2;
              }
          }
          
          public class FizzBuzzRule extends UnitRuleGroup {
          
              public FizzBuzzRule(Object... rules) {
                  for (Object rule : rules) {
                      addRule(rule);
                  }
              }
          
              @Override
              public int getPriority() {
                  return 0;
              }
          }
          
          @Rule
          public class NonFizzBuzzRule {
          
              @Condition
              public boolean isNotFizzNorBuzz(@Fact("number") Integer number) {
                  return number % 5 != 0 || number % 7 != 0;
              }
          
              @Action
              public void printInput(@Fact("number") Integer number) {
                  System.out.print(number);
              }
          
              @Priority
              public int getPriority() {
                  return 3;
              }
          }

          以下是对这些规则的一些解释:

          • FizzRule和BuzzRule很简单,它们会检查输入是5的倍数还是7的倍数,然后打印结果。
          • FizzBuzzRule是一个组合规则。通过FizzRule和BuzzRule创建。基类选择为UnitRuleGroup,要么满足并应用这两个规则,要么什么都不应用。
          • NonFizzBuzzRule是既不是5的倍数也不是7的倍数时的规则。

          请注意,我们已经设置了优先级,因此规则的触发顺序与Java示例中的示例相同。
          然后,我们必须将这些规则注册到一个规则集中,并使用一个规则引擎来触发它们:

          public class FizzBuzzWithEasyRules {
              public static void main(String[] args) {
                  // 创建规则引擎
                  RulesEngineParameters parameters = new RulesEngineParameters().skipOnFirstAppliedRule(true);
                  RulesEngine fizzBuzzEngine = new DefaultRulesEngine(parameters);
          
                  // 创建规则
                  Rules rules = new Rules();
                  rules.register(new FizzRule());
                  rules.register(new BuzzRule());
                  rules.register(new FizzBuzzRule(new FizzRule(), new BuzzRule()));
                  rules.register(new NonFizzBuzzRule());
          
                  // 触发规则
                  Facts facts = new Facts();
                  for (int i = 1; i <= 100; i++) {
                      facts.put("number", i);
                      fizzBuzzEngine.fire(rules, facts);
                      System.out.println();
                  }
              }
          }

          注意,我们已经设置了skipOnFirstAppliedRule参数,以便在成功应用规则时跳过后续的规则。

          到此这篇关于Java规则引擎easy-rules详细介绍的文章就介绍到这了,更多相关Java规则引擎easy-rules详细介绍内容请搜索3672js教程以前的文章或继续浏览下面的相关文章希望大家以后多多支持3672js教程!

          您可能感兴趣的文章:
          • java轻量级规则引擎easy-rules使用介绍
          相关栏目:

          用户点评