乐趣区

关于后端:Java规则引擎easyrules详细介绍

最近在思考一个基于规定进行筛选的技术重构,想通过规定引擎进行实现,借着这个机会正好能够具体理解一下规定引擎。本篇文章将会具体介绍规定引擎 easy-rules 的应用。我的项目地址:github.com/j-easy/easy…
简介
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 引擎能够配置以下参数:
https://zhuanlan.zhihu.com/p/…
https://zhuanlan.zhihu.com/p/…
https://zhuanlan.zhihu.com/p/…
https://zhuanlan.zhihu.com/p/…
https://zhuanlan.zhihu.com/p/…
https://zhuanlan.zhihu.com/p/…
https://zhuanlan.zhihu.com/p/…
https://zhuanlan.zhihu.com/p/…
https://zhuanlan.zhihu.com/p/…
https://zhuanlan.zhihu.com/p/…
https://zhuanlan.zhihu.com/p/…
参数类型默认值 rulePriorityThresholdintMaxIntskipOnFirstAppliedRulebooleanfalserulePriorityThresholdintfalseskipOnFirstFailedRulebooleanfalseskipOnFirstNonTriggeredRulebooleanfalse

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();}
}

}

退出移动版