Easy Rules

一.什么是Easy Rules

Easy Rules 是一款 Java 規(guī)則引擎互例,它的誕生啟發(fā)自有Martin Fowler 一篇名為 "Should I use a Rules Engine?" 文章邮丰。
該文文章寫(xiě)道:您可以自己構(gòu)建一個(gè)簡(jiǎn)單的規(guī)則引擎膊存。您所需要的就是創(chuàng)建一組具有條件和操作的對(duì)象急膀,將它們存儲(chǔ)在集合中,并遍歷它們以評(píng)估條件并執(zhí)行操作馏段。
這正是Easy Rules所做的砍鸠,它提供了規(guī)則抽象來(lái)創(chuàng)建帶有條件和操作的規(guī)則,以及運(yùn)行一組規(guī)則來(lái)評(píng)估條件和執(zhí)行操作的RulesEngine API驶赏。

二.核心特性

  • 輕量級(jí)庫(kù)和易于學(xué)習(xí)的API炸卑;
  • 基于POJO的注釋編程模型開(kāi)發(fā);
  • 用于定義業(yè)務(wù)規(guī)則并使用Java輕松應(yīng)用它們的有用抽象;
  • 能夠從原始規(guī)則創(chuàng)建復(fù)合規(guī)則;
  • 使用表達(dá)式語(yǔ)言定義規(guī)則的能力;

三.運(yùn)行環(huán)境

EasyRules是一個(gè)Java庫(kù)煤傍。它需要Java 1.7+運(yùn)行環(huán)境盖文。

四.maven坐標(biāo)

<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:規(guī)則名稱(chēng)空間中的唯一規(guī)則名稱(chēng)
  • Description:規(guī)則的簡(jiǎn)要說(shuō)明
  • Priority:規(guī)則優(yōu)先級(jí)
  • Facts:規(guī)則運(yùn)行時(shí)已知的一些數(shù)據(jù)
  • Conditions:為了應(yīng)用規(guī)則,應(yīng)根據(jù)某些事實(shí)應(yīng)滿(mǎn)足的條件集
  • Actions:滿(mǎn)足條件時(shí)要執(zhí)行的操作集(可以添加/刪除/修改事實(shí))

2.定義Facts
Facts API是一組規(guī)則需要檢驗(yàn)的facts的抽象蚯姆。在內(nèi)部五续,F(xiàn)acts持有HashMap<String,Object>龄恋,這意味著:

  • Facts 命名必須唯一且不能為空;
  • 任何Java對(duì)象都可以當(dāng)做Fact.

說(shuō)白了 Facts 就是一個(gè) HashMap<String,Object>,里面都是一些需要校驗(yàn)規(guī)則的參數(shù)字段.
下面是一個(gè)定義Facts例子:

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

Facts可以用在condition 和action 方法中,在參數(shù)前面使用@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.規(guī)則引擎
3.1 從3.1版開(kāi)始疙驾,Easy Rules提供了規(guī)則引擎接口的兩種實(shí)現(xiàn):

  • DefaultRulesEngine:根據(jù)它們的自然順序(默認(rèn)優(yōu)先級(jí))應(yīng)用規(guī)則;
  • InferenceRulesEngine:對(duì)已知事實(shí)持續(xù)應(yīng)用規(guī)則,直到不再適用任何規(guī)則郭毕。

3.2 創(chuàng)建規(guī)則引擎
要?jiǎng)?chuàng)建規(guī)則引擎它碎,可以使用每個(gè)實(shí)現(xiàn)的構(gòu)造函數(shù):

RulesEngine rulesEngine = new DefaultRulesEngine();

// or

RulesEngine rulesEngine = new InferenceRulesEngine();

然后,您可以按照以下方式運(yùn)行注冊(cè)規(guī)則:

rulesEngine.fire(rules, facts);

3.3 規(guī)則引擎參數(shù)

  • skipOnFirstAppliedRule參數(shù)告訴引擎在應(yīng)用規(guī)則時(shí)跳過(guò)下一個(gè)規(guī)則显押。
  • skipOnFirstFailedRule參數(shù)告訴引擎在規(guī)則失敗時(shí)跳過(guò)下一個(gè)規(guī)則扳肛。
  • skipOnFirstNonTriggeredRule參數(shù)告訴引擎跳過(guò)下一個(gè)規(guī)則,沒(méi)有觸發(fā)規(guī)則乘碑。
  • rulePriorityThreshold參數(shù)告訴引擎在優(yōu)先級(jí)超過(guò)定義的閾值時(shí)跳過(guò)下一個(gè)規(guī)則挖息。

您可以使用RulesEngineParameters API指定這些參數(shù):

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

如果您想從引擎獲得參數(shù),可以使用以下代碼片段:

RulesEngineParameters parameters = myEngine.getParameters();

六.HelloWorld

1.使用注釋定義規(guī)則
Easy Rules提供了可以將POJO轉(zhuǎn)換為規(guī)則的@Rule注釋蝉仇。下面是一個(gè)例子:

@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注解標(biāo)記了執(zhí)行來(lái)評(píng)估規(guī)則條件的方法旋讹。這個(gè)方法必須是公共的,可能有一個(gè)或多個(gè)參數(shù)注釋@Fact并返回一個(gè)布爾類(lèi)型轿衔。只有一個(gè)方法可以用@Condition注釋進(jìn)行注釋沉迹。

@Action注釋標(biāo)記用于執(zhí)行規(guī)則操作的方法。規(guī)則可以有多個(gè)操作害驹”夼唬可以使用order屬性以指定的順序執(zhí)行操作。默認(rèn)情況下宛官,動(dòng)作的順序是0葫松。

運(yùn)行策略

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

    }
}

七.復(fù)合規(guī)則

EasyRules允許您從原始規(guī)則創(chuàng)建復(fù)雜規(guī)則瓦糕。CompositeRule是由一組規(guī)則組成的。

復(fù)合規(guī)則是一個(gè)抽象概念腋么,因?yàn)榻M合規(guī)則可以以不同的方式觸發(fā)咕娄。在3.2版中,Easy Rules附帶了3種組合規(guī)則的實(shí)現(xiàn):

UnitRuleGroup:單元規(guī)則組是作為一個(gè)單元的復(fù)合規(guī)則:要么應(yīng)用所有規(guī)則珊擂,要么什么都不應(yīng)用圣勒。
ActivationRuleGroup:激活規(guī)則組是一個(gè)復(fù)合規(guī)則,它觸發(fā)第一個(gè)適用規(guī)則摧扇,并忽略組中的其他規(guī)則(XOR邏輯)圣贸。規(guī)則首先按照組內(nèi)的自然順序(默認(rèn)優(yōu)先級(jí))進(jìn)行排序。
ConditionalRuleGroup:條件規(guī)則組是一個(gè)復(fù)合規(guī)則扛稽,其中優(yōu)先級(jí)最高的規(guī)則充當(dāng)條件:如果優(yōu)先級(jí)最高的規(guī)則求值為true吁峻,則觸發(fā)其余規(guī)則。

八.Fizz Buzz

本教程使用簡(jiǎn)單的規(guī)則實(shí)現(xiàn)FizzBuzz應(yīng)用程序在张。FizzBuzz是一個(gè)簡(jiǎn)單的應(yīng)用程序用含,需要從1數(shù)到100,并且:

  • 如果數(shù)字是5的倍數(shù)瞧掺,則打印“fizz”;
  • 如果數(shù)字是7的倍數(shù)耕餐,請(qǐng)打印“buzz”;
  • 如果數(shù)字是5和7的倍數(shù),請(qǐng)打印“fizzbuzz”;
  • 否則打印數(shù)字本身.

下面是一個(gè)來(lái)自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();
  }
}

我們將為每個(gè)需求編寫(xiě)一個(gè)規(guī)則:
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();
        }
    }
}

您應(yīng)該得到以下輸出:
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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末辟狈,一起剝皮案震驚了整個(gè)濱河市肠缔,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌哼转,老刑警劉巖明未,帶你破解...
    沈念sama閱讀 216,544評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異壹蔓,居然都是意外死亡趟妥,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)佣蓉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)披摄,“玉大人,你說(shuō)我怎么就攤上這事勇凭【尾玻” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,764評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵虾标,是天一觀(guān)的道長(zhǎng)寓盗。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么傀蚌? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,193評(píng)論 1 292
  • 正文 為了忘掉前任基显,我火速辦了婚禮,結(jié)果婚禮上善炫,老公的妹妹穿的比我還像新娘撩幽。我一直安慰自己,他們只是感情好箩艺,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,216評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布摸航。 她就那樣靜靜地躺著,像睡著了一般舅桩。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上雨膨,一...
    開(kāi)封第一講書(shū)人閱讀 51,182評(píng)論 1 299
  • 那天擂涛,我揣著相機(jī)與錄音,去河邊找鬼聊记。 笑死撒妈,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的排监。 我是一名探鬼主播狰右,決...
    沈念sama閱讀 40,063評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼舆床!你這毒婦竟也來(lái)了棋蚌?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 38,917評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤挨队,失蹤者是張志新(化名)和其女友劉穎谷暮,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體盛垦,經(jīng)...
    沈念sama閱讀 45,329評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡湿弦,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,543評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了腾夯。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片颊埃。...
    茶點(diǎn)故事閱讀 39,722評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖蝶俱,靈堂內(nèi)的尸體忽然破棺而出班利,到底是詐尸還是另有隱情,我是刑警寧澤跷乐,帶...
    沈念sama閱讀 35,425評(píng)論 5 343
  • 正文 年R本政府宣布肥败,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏馒稍。R本人自食惡果不足惜皿哨,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,019評(píng)論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望纽谒。 院中可真熱鬧证膨,春花似錦、人聲如沸鼓黔。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,671評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)澳化。三九已至崔步,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間缎谷,已是汗流浹背井濒。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,825評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留列林,地道東北人瑞你。 一個(gè)月前我還...
    沈念sama閱讀 47,729評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像希痴,于是被迫代替她去往敵國(guó)和親者甲。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,614評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容