概念理解
描述一個(gè)簡單的處理:基于一堆現(xiàn)實(shí)情況,運(yùn)用規(guī)則引擎、經(jīng)過處理得到對(duì)應(yīng)的結(jié)果,然后再據(jù)此做后續(xù)的事情。
- fact: 事實(shí),已有的現(xiàn)實(shí)情況,即輸入信息
- rules: 規(guī)則集合,由一系列規(guī)則組成,可能有不同的規(guī)則排列
- rule: 規(guī)則,包含基本的判斷條件和條件符合要做的動(dòng)作。
- condition: 規(guī)則的判定條件(特定的判斷邏輯 if else)
- action: 規(guī)則判定符合后執(zhí)行的動(dòng)作
實(shí)例和編碼
一句話描述: 人提著包去酒店買酒,需要判斷是否成年人,成年人才能購買酒,商店據(jù)此賣你酒,你買到了酒就裝包里走人,回家喝酒去。
接下來看easy-rules的定義和處理。
抽象出2條規(guī)則
@Rule(name = "age-rule", description = "age-rule", priority = 1)
public class AgeRule {
@Condition
public boolean isAdult(@Fact("person") Person person) {
return person.getAge() > 18;
}
@Action
public void setAdult(@Fact("person") Person person) {
person.setAdult(true);
}
}
package org.jeasy.rules.tutorials.shop;
import org.jeasy.rules.annotation.Action;
import org.jeasy.rules.annotation.Condition;
import org.jeasy.rules.annotation.Fact;
import org.jeasy.rules.annotation.Rule;
/**
* @author dingqi on 2023/5/26
* @since 1.0.0
*/
@Rule(name = "alcohol-rule", description = "alcohol-rule", priority = 2)
public class AlcoholRule {
@Condition
public boolean shopRule(@Fact("person") Person person) {
return person.isAdult() == true;
}
@Action
public void shopReply(@Fact("bag") Bag bag) {
bag.setSuccess(true);
bag.add("Vodka");
}
}
簡單的規(guī)則引擎
// create a rule set
Rules rules = new Rules();
rules.register(new AgeRule());
rules.register(new AlcoholRule());
//create a default rules engine and fire rules on known facts
DefaultRulesEngine rulesEngine = new DefaultRulesEngine();
事實(shí)1的處理
Facts facts = new Facts();
Person tom = new Person("Tom", 19);
facts.put("person", tom);
Bag bag = new Bag();
facts.put("bag", bag);
System.out.println("Tom: Hi! can I have some Vodka please?");
rulesEngine.fire(rules, facts);
System.out.println("Tom: bag is " + bag);
輸出:Tom成年了,買到了伏特加
Tom: Hi! can I have some Vodka please?
Tom: bag is Bag{success=true, goods=[Vodka]}
事實(shí)2的處理
Person jack = new Person("Jack", 10);
facts.put("person", jack);
Bag bag2 = new Bag();
facts.put("bag", bag2);
System.out.println("Jack: Hi! can I have some Vodka please?");
rulesEngine.fire(rules, facts);
System.out.println("Jack: bag is " + bag2);
輸出:Jack未成年,無功而返
Jack: Hi! can I have some Vodka please?
Jack: bag is Bag{success=false, goods=[]}
easy-rules 規(guī)則的抽象和執(zhí)行
事實(shí)描述
public class Facts implements Iterable<Fact<?>> {
private final Set<Fact<?>> facts = new HashSet<>();
/**
* A class representing a named fact. Facts have unique names within a {@link Facts}
* instance.
*
* @param <T> type of the fact
* @author Mahmoud Ben Hassine
*/
public class Fact<T> {
private final String name;
private final T value;
事實(shí)簡單就是key、value對(duì), 某個(gè)事實(shí)的名稱,和事實(shí)的屬性特征(以一切皆對(duì)象來看,就是一個(gè)一個(gè)的對(duì)象組成了事實(shí))。(只要在規(guī)則條件真正執(zhí)行前,能明確這些事實(shí)就行)
規(guī)則的抽象
- 名稱
- 描述
- 優(yōu)先級(jí)
- 執(zhí)行Facts的的方法
見org.jeasy.rules.api.Rule
接口 和基礎(chǔ)實(shí)現(xiàn)類org.jeasy.rules.core.BasicRule
條件和動(dòng)作注解:
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Condition {
}
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Condition {
}
默認(rèn)的規(guī)則
class DefaultRule extends BasicRule {
private final Condition condition;
private final List<Action> actions;
DefaultRule(String name, String description, int priority, Condition condition, List<Action> actions) {
super(name, description, priority);
this.condition = condition;
this.actions = actions;
}
@Override
public boolean evaluate(Facts facts) {
return condition.evaluate(facts);
}
@Override
public void execute(Facts facts) throws Exception {
for (Action action : actions) {
action.execute(facts);
}
}
}
動(dòng)態(tài)代理執(zhí)行規(guī)則和動(dòng)作
使用org.jeasy.rules.api.Rules
添加規(guī)則時(shí)如下:
- org.jeasy.rules.api.Rules#register
public void register(Object... rules) {
Objects.requireNonNull(rules);
for (Object rule : rules) {
Objects.requireNonNull(rule);
this.rules.add(RuleProxy.asRule(rule));
}
}
使用org.jeasy.rules.annotation.Rule
注解構(gòu)造的規(guī)則是使用RuleProxy構(gòu)造的
規(guī)則的執(zhí)行:org.jeasy.rules.core.DefaultRulesEngine#doFire
void doFire(Rules rules, Facts facts) {
if (rules.isEmpty()) {
LOGGER.warn("No rules registered! Nothing to apply");
return;
}
logEngineParameters();
log(rules);
log(facts);
LOGGER.debug("Rules evaluation started");
for (Rule rule : rules) {
final String name = rule.getName();
final int priority = rule.getPriority();
if (priority > parameters.getPriorityThreshold()) {
LOGGER.debug("Rule priority threshold ({}) exceeded at rule '{}' with priority={}, next rules will be skipped",
parameters.getPriorityThreshold(), name, priority);
break;
}
if (!shouldBeEvaluated(rule, facts)) {
LOGGER.debug("Rule '{}' has been skipped before being evaluated", name);
continue;
}
boolean evaluationResult = false;
try {
evaluationResult = rule.evaluate(facts);
} catch (RuntimeException exception) {
LOGGER.error("Rule '" + name + "' evaluated with error", exception);
triggerListenersOnEvaluationError(rule, facts, exception);
// give the option to either skip next rules on evaluation error or continue by considering the evaluation error as false
if (parameters.isSkipOnFirstNonTriggeredRule()) {
LOGGER.debug("Next rules will be skipped since parameter skipOnFirstNonTriggeredRule is set");
break;
}
}
if (evaluationResult) {
LOGGER.debug("Rule '{}' triggered", name);
triggerListenersAfterEvaluate(rule, facts, true);
try {
triggerListenersBeforeExecute(rule, facts);
rule.execute(facts);
LOGGER.debug("Rule '{}' performed successfully", name);
triggerListenersOnSuccess(rule, facts);
if (parameters.isSkipOnFirstAppliedRule()) {
LOGGER.debug("Next rules will be skipped since parameter skipOnFirstAppliedRule is set");
break;
}
} catch (Exception exception) {
LOGGER.error("Rule '" + name + "' performed with error", exception);
triggerListenersOnFailure(rule, exception, facts);
if (parameters.isSkipOnFirstFailedRule()) {
LOGGER.debug("Next rules will be skipped since parameter skipOnFirstFailedRule is set");
break;
}
}
} else {
LOGGER.debug("Rule '{}' has been evaluated to false, it has not been executed", name);
triggerListenersAfterEvaluate(rule, facts, false);
if (parameters.isSkipOnFirstNonTriggeredRule()) {
LOGGER.debug("Next rules will be skipped since parameter skipOnFirstNonTriggeredRule is set");
break;
}
}
}
}
默認(rèn)的規(guī)則引擎直接遍歷規(guī)則去執(zhí)行,如果condition執(zhí)行命中后,則去執(zhí)行action
public class RuleProxy implements InvocationHandler
private Object evaluateMethod(final Object[] args) throws IllegalAccessException, InvocationTargetException {
Facts facts = (Facts) args[0];
Method conditionMethod = getConditionMethod();
try {
List<Object> actualParameters = getActualParameters(conditionMethod, facts);
return conditionMethod.invoke(target, actualParameters.toArray()); // validated upfront
} catch (NoSuchFactException e) {
LOGGER.warn("Rule '{}' has been evaluated to false due to a declared but missing fact '{}' in {}",
getTargetClass().getName(), e.getMissingFact(), facts);
return false;
} catch (IllegalArgumentException e) {
LOGGER.warn("Types of injected facts in method '{}' in rule '{}' do not match parameters types",
conditionMethod.getName(), getTargetClass().getName(), e);
return false;
}
}
規(guī)則執(zhí)行監(jiān)聽器
在規(guī)則執(zhí)行的過程中,可以做各種操作??梢钥闯梢?guī)則的擴(kuò)展點(diǎn)
/**
* A listener for rule execution events.
*
* @author Mahmoud Ben Hassine (mahmoud.benhassine@icloud.com)
*/
public interface RuleListener {
/**
* Triggered before the evaluation of a rule.
*
* @param rule being evaluated
* @param facts known before evaluating the rule
* @return true if the rule should be evaluated, false otherwise
*/
default boolean beforeEvaluate(Rule rule, Facts facts) {
return true;
}
/**
* Triggered after the evaluation of a rule.
*
* @param rule that has been evaluated
* @param facts known after evaluating the rule
* @param evaluationResult true if the rule evaluated to true, false otherwise
*/
default void afterEvaluate(Rule rule, Facts facts, boolean evaluationResult) { }
/**
* Triggered on condition evaluation error due to any runtime exception.
*
* @param rule that has been evaluated
* @param facts known while evaluating the rule
* @param exception that happened while attempting to evaluate the condition.
*/
default void onEvaluationError(Rule rule, Facts facts, Exception exception) { }
/**
* Triggered before the execution of a rule.
*
* @param rule the current rule
* @param facts known facts before executing the rule
*/
default void beforeExecute(Rule rule, Facts facts) { }
/**
* Triggered after a rule has been executed successfully.
*
* @param rule the current rule
* @param facts known facts after executing the rule
*/
default void onSuccess(Rule rule, Facts facts) { }
/**
* Triggered after a rule has failed.
*
* @param rule the current rule
* @param facts known facts after executing the rule
* @param exception the exception thrown when attempting to execute the rule
*/
default void onFailure(Rule rule, Facts facts, Exception exception) { }
}
回顧規(guī)則執(zhí)行和監(jiān)聽器的執(zhí)行過程
// 1. 條件執(zhí)行前
triggerListenersBeforeEvaluate(rule, facts);
try {
evaluationResult = rule.evaluate(facts);
} catch(Exception e){
// 2. 條件執(zhí)行失敗
triggerListenersOnEvaluationError(rule, facts, exception);
}
if (evaluationResult) {
// 3. 條件執(zhí)行后(條件滿足)
triggerListenersAfterEvaluate(rule, facts, true);
try {
// 4. 動(dòng)作執(zhí)行前
triggerListenersBeforeExecute(rule, facts);
rule.execute(facts);
// 5. 動(dòng)作執(zhí)行后
triggerListenersOnSuccess(rule, facts);
} catch (Exception exception) {
// 6. 條件執(zhí)行失敗
triggerListenersOnFailure(rule, exception, facts);
}
}else{
// 3. 條件執(zhí)行后(條件不滿足)
triggerListenersAfterEvaluate(rule, facts, false);
}
擴(kuò)展
- Java Expression Language (JEXL) :表達(dá)式語言引擎
https://commons.apache.org/proper/commons-jexl/apidocs/org/apache/commons/jexl3/JexlEngine.html
-
MVEL:一個(gè)功能強(qiáng)大的基于Java應(yīng)用程序的表達(dá)式語言。文章來源:http://www.zghlxwxcb.cn/news/detail-466216.html
-
SpEL:Spring表達(dá)式語言文章來源地址http://www.zghlxwxcb.cn/news/detail-466216.html
name: adult rule
description: when age is greater than 18, then mark as adult
priority: 1
condition: "#{ ['person'].age > 18 }"
actions:
- "#{ ['person'].setAdult(true) }"
到了這里,關(guān)于規(guī)則引擎入門-基于easy-rules的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!