Java規則引擎easy-rules如何理解,很多新手對此不是很清楚,為了幫助大家解決這個難題,下面小編將為大家詳細講解,有這方面需求的人可以來學習下,希望你能有所收獲。
最近在思考一個基于規則進行挑選的技術重構,想通過規則引擎進行實現,借著這個機會正好可以詳細了解一下規則引擎。下面將會詳細介紹規則引擎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注解用來標記執行操作的方法,規則可以有多個操作??梢允褂胦rder屬性以指定的順序執行操作。
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()方法指定優先級。
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允許我們在觸發整個規則集之前/之后提供自定義行為??梢允褂萌缦路绞阶员O聽器。
DefaultRulesEngine rulesEngine = new DefaultRulesEngine(); rulesEngine.registerRulesEngineListener(myRulesEngineListener);
可以注冊任意數量的監聽器,它們將按照注冊順序執行。
Easy Rules支持用MVEL、SpEL和JEXL定義規則。
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??梢允褂肦uleListener#onEvaluationError來監聽評估錯誤。
關于操作中不正確表達式的引擎行為
對于任何在執行操作時可能發生的運行時異常(丟失事實、表達式中輸入錯誤等),該操作將不會執行,引擎將記錄一個錯誤??梢允褂肦uleListener#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創建?;愡x擇為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參數,以便在成功應用規則時跳過后續的規則。
看完上述內容是否對您有幫助呢?如果還想對相關知識有進一步的了解或閱讀更多相關文章,請關注億速云行業資訊頻道,感謝您對億速云的支持。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。