Easy Rules

一.什么是Easy Rules

Easy Rules 是一款 Java 规则引擎,它的诞生启发自有Martin Fowler 一篇名为 "Should I use a Rules Engine?" 文章。
该文文章写道:您可以自己构建一个简单的规则引擎。您所需要的就是创建一组具有条件和操作的对象,将它们存储在集合中,并遍历它们以评估条件并执行操作。
这正是Easy Rules所做的,它提供了规则抽象来创建带有条件和操作的规则,以及运行一组规则来评估条件和执行操作的RulesEngine API。

二.核心特性

  • 轻量级库和易于学习的API;
  • 基于POJO的注释编程模型开发;
  • 用于定义业务规则并使用Java轻松应用它们的有用抽象;
  • 能够从原始规则创建复合规则;
  • 使用表达式语言定义规则的能力;

三.运行环境

EasyRules是一个Java库。它需要Java 1.7+运行环境。

四.maven坐标

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

<dependency>
    <groupId>org.jeasy</groupId>
    <artifactId>easy-rules-support</artifactId>
    <version>3.2.0</version>
</dependency>

<dependency>
    <groupId>org.jeasy</groupId>
    <artifactId>easy-rules-mvel</artifactId>
    <version>3.2.0</version>
</dependency>

五.

1.名词解释

  • Name:规则名称空间中的唯一规则名称
  • Description:规则的简要说明
  • Priority:规则优先级
  • Facts:规则运行时已知的一些数据
  • Conditions:为了应用规则,应根据某些事实应满足的条件集
  • Actions:满足条件时要执行的操作集(可以添加/删除/修改事实)

2.定义Facts
Facts API是一组规则需要检验的facts的抽象。在内部,Facts持有HashMap<String,Object>,这意味着:

  • Facts 命名必须唯一且不能为空;
  • 任何Java对象都可以当做Fact.

说白了 Facts 就是一个 HashMap<String,Object>,里面都是一些需要校验规则的参数字段.
下面是一个定义Facts例子:

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

Facts可以用在condition 和action 方法中,在参数前面使用@Fact注解:

@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
    }
}

3.规则引擎
3.1 从3.1版开始,Easy Rules提供了规则引擎接口的两种实现:

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

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

RulesEngine rulesEngine = new DefaultRulesEngine();

// or

RulesEngine rulesEngine = new InferenceRulesEngine();

然后,您可以按照以下方式运行注册规则:

rulesEngine.fire(rules, facts);

3.3 规则引擎参数

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

六.HelloWorld

1.使用注释定义规则
Easy Rules提供了可以将POJO转换为规则的@Rule注释。下面是一个例子:

@Rule(name = "Hello World rule", description = "Always say hello world")
public class HelloWorldRule {

    @Condition
    public boolean when() {
        return true;
    }

    @Action
    public void then() throws Exception {
        System.out.println("hello world");
    }

}

@Condition注解标记了执行来评估规则条件的方法。这个方法必须是公共的,可能有一个或多个参数注释@Fact并返回一个布尔类型。只有一个方法可以用@Condition注释进行注释。

@Action注释标记用于执行规则操作的方法。规则可以有多个操作。可以使用order属性以指定的顺序执行操作。默认情况下,动作的顺序是0。

运行策略

public class Launcher {

    public static void main(String[] args) {

        // create facts
        Facts facts = new Facts();

        // create rules
        Rules rules = new Rules();
        rules.register(new HelloWorldRule());

        // create a rules engine and fire rules on known facts
        RulesEngine rulesEngine = new DefaultRulesEngine();
        rulesEngine.fire(rules, facts);

    }
}

七.复合规则

EasyRules允许您从原始规则创建复杂规则。CompositeRule是由一组规则组成的。

复合规则是一个抽象概念,因为组合规则可以以不同的方式触发。在3.2版中,Easy Rules附带了3种组合规则的实现:

UnitRuleGroup:单元规则组是作为一个单元的复合规则:要么应用所有规则,要么什么都不应用。
ActivationRuleGroup:激活规则组是一个复合规则,它触发第一个适用规则,并忽略组中的其他规则(XOR逻辑)。规则首先按照组内的自然顺序(默认优先级)进行排序。
ConditionalRuleGroup:条件规则组是一个复合规则,其中优先级最高的规则充当条件:如果优先级最高的规则求值为true,则触发其余规则。

八.Fizz Buzz

本教程使用简单的规则实现FizzBuzz应用程序。FizzBuzz是一个简单的应用程序,需要从1数到100,并且:

  • 如果数字是5的倍数,则打印“fizz”;
  • 如果数字是7的倍数,请打印“buzz”;
  • 如果数字是5和7的倍数,请打印“fizzbuzz”;
  • 否则打印数字本身.

下面是一个来自Java示例的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();
  }
}

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

@Rule(name = "FizzRule", description = "FizzRule description", priority = 1)
public class FizzRule {

    @Condition
    public boolean isFizz(@Fact("number") Integer number) {
        return number % 5 == 0;
    }

    @Action
    public void printFizz() {
        System.out.print("fizz");
    }
}

BuzzRule

@Rule(name = "BuzzRule", description = "BuzzRule description", priority = 2)
public class BuzzRule {

    @Condition
    public boolean isBuzz(@Fact("number") Integer number) {
        return number % 7 == 0;
    }

    @Action
    public void printBuzz(@Fact("number") Integer number) {
        System.out.print("buzz");
    }
}

FizzBuzzRule

public class FizzBuzzRule extends UnitRuleGroup {

    public FizzBuzzRule(Object... rules) {
        for (Object rule : rules) {
            addRule(rule);
        }
    }

    @Override
    public int getPriority() {
        return 0;
    }
}

NonFizzBuzzRule

@Rule
public class NonFizzBuzzRule {

    @Condition
    public boolean isNotFizzNorBuzz(@Fact("number") Integer number) {
        // can return true, because this is the latest rule to trigger according to assigned priorities
        // and in which case, the number is not fizz nor buzz
        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;
    }
}

FizzBuzzWithEasyRules

public class FizzBuzzWithEasyRules {
    public static void main(String[] args) {
        // create a rules engine
        RulesEngineParameters parameters = new RulesEngineParameters().skipOnFirstAppliedRule(true);
        RulesEngine fizzBuzzEngine = new DefaultRulesEngine(parameters);

        // create rules
        Rules rules = new Rules();
        rules.register(new FizzRule());
        rules.register(new BuzzRule());
        rules.register(new FizzBuzzRule(new FizzRule(), new BuzzRule()));
        rules.register(new NonFizzBuzzRule());

        // fire rules
        Facts facts = new Facts();
        for (int i = 1; i <= 100; i++) {
            facts.put("number", i);
            fizzBuzzEngine.fire(rules, facts);
            System.out.println();
        }
    }
}

您应该得到以下输出:
1
2
3
4
fizz
6
buzz
8
9
fizz
11
12
13
buzz
fizz
16
17
18
19
fizz
buzz
22
23
24
fizz
26
27
buzz
29
fizz
31
32
33
34
fizzbuzz
36
37
38
39
fizz
41
buzz
43
44
fizz
46
47
48
buzz
fizz
51
52
53
54
fizz
buzz
57
58
59
fizz
61
62
buzz
64
fizz
66
67
68
69
fizzbuzz
71
72
73
74
fizz
76
buzz
78
79
fizz
81
82
83
buzz
fizz
86
87
88
89
fizz
buzz
92
93
94
fizz
96
97
buzz
99
fizz

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 151,511评论 1 330
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 64,495评论 1 273
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 101,595评论 0 225
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 42,558评论 0 190
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 50,715评论 3 270
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 39,672评论 1 192
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,112评论 2 291
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 29,837评论 0 181
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,417评论 0 228
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 29,928评论 2 232
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,316评论 1 242
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 27,773评论 2 234
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,253评论 3 220
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,827评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,440评论 0 180
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 34,523评论 2 249
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 34,583评论 2 249

推荐阅读更多精彩内容