Java規則引擎easy-rules詳細介紹
最近在思考一個基於規則進行挑選的技術重構,想通過規則引擎進行實現,借著這個機會正好可以詳細瞭解一下規則引擎。本篇文章將會詳細介紹規則引擎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引擎可以配置以下參數:
參數 | 類型 | 默認值 |
---|---|---|
rulePriorityThreshold | int | MaxInt |
skipOnFirstAppliedRule | boolean | false |
rulePriorityThreshold | int | false |
skipOnFirstFailedRule | boolean | false |
skipOnFirstNonTriggeredRule | boolean | false |
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詳細介紹內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- Java guava monitor監視器線程的使用詳解
- 前端Vue中常用rules校驗規則詳解
- .NET RulesEngine(規則引擎)的使用詳解
- java對象對比之comparable和comparator的區別
- Vue實現動態查詢規則生成組件