本文主要是介绍EasyRule源码:EasyRule框架源码分析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
目录
1.简要介绍EasyRule
2.从实例入手看EasyRule使用方法
3.执行过程源码分析
3.1 Fact&Facts
3.2 规则定义和注册
3.2.1 Rule接口
3.2.2 规则注册管理
3.2.3 创建规则代理
3.3 规则引擎调度执行
1.简要介绍EasyRule
当下的规则引擎选择非常多,例如 EasyRule、Aviator 、QLExpress、Drools、Zools等;前面的文章也重点分析了Aviator框架源码,相关文章见:
1.【精选】Aviator源码:从具体实例看Aviator属性语法糖源码分析(a.b.c)_aviator 变量语法糖_mumubili的博客-CSDN博客
2. Aviator源码:Aviator表达式引擎执行过程源码分析_表达式引擎aviator-CSDN博客
本篇对另一种常用的规则引擎框架EasyRule进行源码分析;
EasyRule作为一种小而美的轻量级的规则引擎开源框架,从实际业务规则应用场景出发,抽象出了条件(Condition)、动作(Action)、规则(Rule)数据模型,并通过规则引擎(RulesEngine)完成规则的调度执行。
EasyRule的主要特性归纳如下:
-
轻量级框架和易于学习的API
-
基于POJO的开发与注解的编程模型
-
定义抽象的业务规则并轻松应用它们
-
支持从简单规则创建组合规则的能力
-
支持使用表达式语言(如MVEL和SpEL)定义规则的能力
本文对EasyRule归纳性的描述不再过多赘述,下面主要着重对EasyRule框架源码进行分析;
2.从实例入手看EasyRule使用方法
EasyRule源码大家可以在github上找到:Github之EasyRule
这里选取源码中Tutorial模块中的例子进行说明:
@Rule(name = "weather rule", description = "if it rains then take an umbrella")
public class WeatherRule {@Conditionpublic boolean itRains(@Fact("rain") boolean rain) {return rain;}@Actionpublic void takeAnUmbrella() {System.out.println("It rains, take an umbrella!");}
}
这里通过注解模式对业务规则进行定义;
- @Rule:表明该类为一个规则类
- @Condition:表明规则类中的方法作为规则条件
- @Fact:作为条件中的传参
- @Action:作为规则条件命中之后具体执行的动作
完成规则类的定义后,通过启动测试验证类,验证规则的执行情况:
public class Launcher {public static void main(String[] args) {// define factsFacts facts = new Facts();facts.put("rain", true);// define rulesWeatherRule weatherRule = new WeatherRule();Rules rules = new Rules();rules.register(weatherRule);// fire rules on known factsRulesEngine rulesEngine = new DefaultRulesEngine();rulesEngine.fire(rules, facts);}}
3.执行过程源码分析
上述的启动类中,只要包含3部分:
1)定义并初始化Facts
2)定义并注册规则
3)定义规则引擎,并进行规则调度
下面分别对这3部分进行分析
3.1 Fact&Facts
Fact&Facts源码如下:
public class Fact<T> {private final String name;private final T value;/*** Create a new fact.* @param name of the fact* @param value of the fact*/public Fact(String name, T value) {Objects.requireNonNull(name, "name must not be null");Objects.requireNonNull(value, "value must not be null");this.name = name;this.value = value;}/*** Get the fact name.* @return fact name*/public String getName() {return name;}/*** Get the fact value.* @return fact value*/public T getValue() {return value;}@Overridepublic String toString() {return "Fact{" +"name='" + name + '\'' +", value=" + value +'}';}/** The Facts API represents a namespace for facts where each fact has a unique name.* Hence, equals/hashcode are deliberately calculated only on the fact name.*/@Overridepublic boolean equals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;Fact<?> fact = (Fact<?>) o;return name.equals(fact.name);}@Overridepublic int hashCode() {return Objects.hash(name);}
}
/*** This class encapsulates a set of facts and represents a facts namespace.* Facts have unique names within a <code>Facts</code> object.** @author Mahmoud Ben Hassine (mahmoud.benhassine@icloud.com)*/
public class Facts implements Iterable<Fact<?>> {private final Set<Fact<?>> facts = new HashSet<>();/*** Add a fact, replacing any fact with the same name.** @param name of the fact to add, must not be null* @param value of the fact to add, must not be null*/public <T> void put(String name, T value) {Objects.requireNonNull(name, "fact name must not be null");Objects.requireNonNull(value, "fact value must not be null");Fact<?> retrievedFact = getFact(name);if (retrievedFact != null) {remove(retrievedFact);}add(new Fact<>(name, value));}/*** Add a fact, replacing any fact with the same name.* * @param fact to add, must not be null*/public <T> void add(Fact<T> fact) {Objects.requireNonNull(fact, "fact must not be null");Fact<?> retrievedFact = getFact(fact.getName());if (retrievedFact != null) {remove(retrievedFact);}facts.add(fact);}/*** Remove a fact by name.** @param factName name of the fact to remove, must not be null*/public void remove(String factName) {Objects.requireNonNull(factName, "fact name must not be null");Fact<?> fact = getFact(factName);if (fact != null) {remove(fact);}}/*** Remove a fact.** @param fact to remove, must not be null*/public <T> void remove(Fact<T> fact) {Objects.requireNonNull(fact, "fact must not be null");facts.remove(fact);}/*** Get the value of a fact by its name. This is a convenience method provided* as a short version of {@code getFact(factName).getValue()}.** @param factName name of the fact, must not be null* @param <T> type of the fact's value* @return the value of the fact having the given name, or null if there is* no fact with the given name*/@SuppressWarnings("unchecked")public <T> T get(String factName) {Objects.requireNonNull(factName, "fact name must not be null");Fact<?> fact = getFact(factName);if (fact != null) {return (T) fact.getValue();}return null;}/*** Get a fact by name.** @param factName name of the fact, must not be null* @return the fact having the given name, or null if there is no fact with the given name*/public Fact<?> getFact(String factName) {Objects.requireNonNull(factName, "fact name must not be null");return facts.stream().filter(fact -> fact.getName().equals(factName)).findFirst().orElse(null);}/*** Return a copy of the facts as a map. It is not intended to manipulate* facts outside of the rules engine (aka other than manipulating them through rules).** @return a copy of the current facts as a {@link HashMap}*/public Map<String, Object> asMap() {Map<String, Object> map = new HashMap<>();for (Fact<?> fact : facts) {map.put(fact.getName(), fact.getValue());}return map;}/*** Return an iterator on the set of facts. It is not intended to remove* facts using this iterator outside of the rules engine (aka other than doing it through rules)* * @return an iterator on the set of facts*/@Overridepublic Iterator<Fact<?>> iterator() {return facts.iterator();}/*** Clear facts.*/public void clear() {facts.clear();}@Overridepublic String toString() {Iterator<Fact<?>> iterator = facts.iterator();StringBuilder stringBuilder = new StringBuilder("[");while (iterator.hasNext()) {stringBuilder.append(iterator.next().toString());if (iterator.hasNext()) {stringBuilder.append(",");}}stringBuilder.append("]");return stringBuilder.toString();}
}
Fact:封装了对象名称到实际对象引用的映射
Facts:对Fact进一步封装为Set集合,以及对应的集合操作
3.2 规则定义和注册
3.2.1 Rule接口
规则Rule整体类图如下:
BasicRule:Rule接口的基础实现类,管理名称、描述和优先级
DefaultRule:默认规则实现类,包含Condition和多个Action
SpELRule&MVELRule&JexlRule:支持SpEL、MVEL、Jexl表达式定义的Condition和Action
CompositeRule:聚合规则,多个规则聚合管理
ActivationRuleGroup&ConditionalRuleGroup&UnitRuleGroup:封装不同的聚合规则管理策略
Rules:负责规则的注册管理,其中完成了规则的代理过程
Rule接口定义如下,主要包含了规则的名称、描述、优先级等属性定义:
/*** Abstraction for a rule that can be fired by a rules engine.** Rules are registered in a namespace of rule of type {@link Rules}* in which they must have a <strong>unique</strong> name.** @author Mahmoud Ben Hassine (mahmoud.benhassine@icloud.com)*/
public interface Rule extends Comparable<Rule> {/*** Default rule name.*/String DEFAULT_NAME = "rule";/*** Default rule description.*/String DEFAULT_DESCRIPTION = "description";/*** Default rule priority.*/int DEFAULT_PRIORITY = Integer.MAX_VALUE - 1;/*** Getter for rule name.* @return the rule name*/default String getName() {return DEFAULT_NAME;}/*** Getter for rule description.* @return rule description*/default String getDescription() {return DEFAULT_DESCRIPTION;}/*** Getter for rule priority.* @return rule priority*/default int getPriority() {return DEFAULT_PRIORITY;}/*** This method implements the rule's condition(s).* <strong>Implementations should handle any runtime exception and return true/false accordingly</strong>** @return true if the rule should be applied given the provided facts, false otherwise*/boolean evaluate(Facts facts);/*** This method implements the rule's action(s).* @throws Exception thrown if an exception occurs when performing action(s)*/void execute(Facts facts) throws Exception;}
3.2.2 规则注册管理
对规则的注册是通过Rules类完成的,通过调用register和unregister方法完成规则的注册管理;
/*** This class encapsulates a set of rules and represents a rules namespace.* Rules must have a unique name within a rules namespace.* * Rules will be compared to each other based on {@link Rule#compareTo(Object)}* method, so {@link Rule}'s implementations are expected to correctly implement* {@code compareTo} to ensure unique rule names within a single namespace.** @author Mahmoud Ben Hassine (mahmoud.benhassine@icloud.com)*/
public class Rules implements Iterable<Rule> {private Set<Rule> rules = new TreeSet<>();/*** Create a new {@link Rules} object.** @param rules to register*/public Rules(Set<Rule> rules) {this.rules = new TreeSet<>(rules);}/*** Create a new {@link Rules} object.** @param rules to register*/public Rules(Rule... rules) {Collections.addAll(this.rules, rules);}/*** Create a new {@link Rules} object.** @param rules to register*/public Rules(Object... rules) {this.register(rules);}/*** Register one or more new rules.** @param rules to register, must not be null*/public void register(Object... rules) {Objects.requireNonNull(rules);for (Object rule : rules) {Objects.requireNonNull(rule);this.rules.add(RuleProxy.asRule(rule));}}/*** Unregister one or more rules.** @param rules to unregister, must not be null*/public void unregister(Object... rules) {Objects.requireNonNull(rules);for (Object rule : rules) {Objects.requireNonNull(rule);this.rules.remove(RuleProxy.asRule(rule));}}/*** Unregister a rule by name.** @param ruleName name of the rule to unregister, must not be null*/public void unregister(final String ruleName) {Objects.requireNonNull(ruleName);Rule rule = findRuleByName(ruleName);if (rule != null) {unregister(rule);}}/*** Check if the rule set is empty.** @return true if the rule set is empty, false otherwise*/public boolean isEmpty() {return rules.isEmpty();}/*** Clear rules.*/public void clear() {rules.clear();}/*** Return how many rules are currently registered.** @return the number of rules currently registered*/public int size() {return rules.size();}/*** Return an iterator on the rules set. It is not intended to remove rules* using this iterator.* @return an iterator on the rules set*/@Overridepublic Iterator<Rule> iterator() {return rules.iterator();}private Rule findRuleByName(String ruleName) {return rules.stream().filter(rule -> rule.getName().equalsIgnoreCase(ruleName)).findFirst().orElse(null);}
}
3.2.3 创建规则代理
在上面规则注册的方法中,通过调用RuleProxy.asRule(rule)完成规则的代理,实际注册的是规则的代理类,下面剖析下代理类的构造过程:
/*** Main class to create rule proxies from annotated objects.** @author Mahmoud Ben Hassine (mahmoud.benhassine@icloud.com)*/
public class RuleProxy implements InvocationHandler {private final Object target;private String name;private String description;private Integer priority;private Method[] methods;private Method conditionMethod;private Set<ActionMethodOrderBean> actionMethods;private Method compareToMethod;private Method toStringMethod;private org.jeasy.rules.annotation.Rule annotation;private static final RuleDefinitionValidator ruleDefinitionValidator = new RuleDefinitionValidator();private static final Logger LOGGER = LoggerFactory.getLogger(RuleProxy.class);/*** Makes the rule object implement the {@link Rule} interface.** @param rule the annotated rule object.* @return a proxy that implements the {@link Rule} interface.*/public static Rule asRule(final Object rule) {Rule result;if (rule instanceof Rule) {result = (Rule) rule;} else {ruleDefinitionValidator.validateRuleDefinition(rule);result = (Rule) Proxy.newProxyInstance(Rule.class.getClassLoader(),new Class[]{Rule.class, Comparable.class},new RuleProxy(rule));}return result;}private RuleProxy(final Object target) {this.target = target;}@Overridepublic Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {String methodName = method.getName();switch (methodName) {case "getName":return getRuleName();case "getDescription":return getRuleDescription();case "getPriority":return getRulePriority();case "compareTo":return compareToMethod(args);case "evaluate":return evaluateMethod(args);case "execute":return executeMethod(args);case "equals":return equalsMethod(args);case "hashCode":return hashCodeMethod();case "toString":return toStringMethod();default:return null;}}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;}}private Object executeMethod(final Object[] args) throws IllegalAccessException, InvocationTargetException {Facts facts = (Facts) args[0];for (ActionMethodOrderBean actionMethodBean : getActionMethodBeans()) {Method actionMethod = actionMethodBean.getMethod();List<Object> actualParameters = getActualParameters(actionMethod, facts);actionMethod.invoke(target, actualParameters.toArray());}return null;}private Object compareToMethod(final Object[] args) throws Exception {Method compareToMethod = getCompareToMethod();Object otherRule = args[0]; // validated upfrontif (compareToMethod != null && Proxy.isProxyClass(otherRule.getClass())) {if (compareToMethod.getParameters().length != 1) {throw new IllegalArgumentException("compareTo method must have a single argument");}RuleProxy ruleProxy = (RuleProxy) Proxy.getInvocationHandler(otherRule);return compareToMethod.invoke(target, ruleProxy.getTarget());} else {return compareTo((Rule) otherRule);}}private List<Object> getActualParameters(Method method, Facts facts) {List<Object> actualParameters = new ArrayList<>();Annotation[][] parameterAnnotations = method.getParameterAnnotations();for (Annotation[] annotations : parameterAnnotations) {if (annotations.length == 1) {String factName = ((Fact) (annotations[0])).value(); //validated upfront.Object fact = facts.get(factName);if (fact == null && !facts.asMap().containsKey(factName)) {throw new NoSuchFactException(format("No fact named '%s' found in known facts: %n%s", factName, facts), factName);}actualParameters.add(fact);} else {actualParameters.add(facts); //validated upfront, there may be only one parameter not annotated and which is of type Facts.class}}return actualParameters;}private boolean equalsMethod(final Object[] args) throws Exception {if (!(args[0] instanceof Rule)) {return false;}Rule otherRule = (Rule) args[0];int otherPriority = otherRule.getPriority();int priority = getRulePriority();if (priority != otherPriority) {return false;}String otherName = otherRule.getName();String name = getRuleName();if (!name.equals(otherName)) {return false;}String otherDescription = otherRule.getDescription();String description = getRuleDescription();return Objects.equals(description, otherDescription);}private int hashCodeMethod() throws Exception {int result = getRuleName().hashCode();int priority = getRulePriority();String description = getRuleDescription();result = 31 * result + (description != null ? description.hashCode() : 0);result = 31 * result + priority;return result;}private Method getToStringMethod() {if (this.toStringMethod == null) {Method[] methods = getMethods();for (Method method : methods) {if ("toString".equals(method.getName())) {this.toStringMethod = method;return this.toStringMethod;}}}return this.toStringMethod;}private String toStringMethod() throws Exception {Method toStringMethod = getToStringMethod();if (toStringMethod != null) {return (String) toStringMethod.invoke(target);} else {return getRuleName();}}private int compareTo(final Rule otherRule) throws Exception {int otherPriority = otherRule.getPriority();int priority = getRulePriority();if (priority < otherPriority) {return -1;} else if (priority > otherPriority) {return 1;} else {String otherName = otherRule.getName();String name = getRuleName();return name.compareTo(otherName);}}private int getRulePriority() throws Exception {if (this.priority == null) {int priority = Rule.DEFAULT_PRIORITY;org.jeasy.rules.annotation.Rule rule = getRuleAnnotation();if (rule.priority() != Rule.DEFAULT_PRIORITY) {priority = rule.priority();}Method[] methods = getMethods();for (Method method : methods) {if (method.isAnnotationPresent(Priority.class)) {priority = (int) method.invoke(target);break;}}this.priority = priority;}return this.priority;}private Method getConditionMethod() {if (this.conditionMethod == null) {Method[] methods = getMethods();for (Method method : methods) {if (method.isAnnotationPresent(Condition.class)) {this.conditionMethod = method;return this.conditionMethod;}}}return this.conditionMethod;}private Set<ActionMethodOrderBean> getActionMethodBeans() {if (this.actionMethods == null) {this.actionMethods = new TreeSet<>();Method[] methods = getMethods();for (Method method : methods) {if (method.isAnnotationPresent(Action.class)) {Action actionAnnotation = method.getAnnotation(Action.class);int order = actionAnnotation.order();this.actionMethods.add(new ActionMethodOrderBean(method, order));}}}return this.actionMethods;}private Method getCompareToMethod() {if (this.compareToMethod == null) {Method[] methods = getMethods();for (Method method : methods) {if (method.getName().equals("compareTo")) {this.compareToMethod = method;return this.compareToMethod;}}}return this.compareToMethod;}private Method[] getMethods() {if (this.methods == null) {this.methods = getTargetClass().getMethods();}return this.methods;}private org.jeasy.rules.annotation.Rule getRuleAnnotation() {if (this.annotation == null) {this.annotation = Utils.findAnnotation(org.jeasy.rules.annotation.Rule.class, getTargetClass());}return this.annotation;}private String getRuleName() {if (this.name == null) {org.jeasy.rules.annotation.Rule rule = getRuleAnnotation();this.name = rule.name().equals(Rule.DEFAULT_NAME) ? getTargetClass().getSimpleName() : rule.name();}return this.name;}private String getRuleDescription() {if (this.description == null) {// Default description = "when " + conditionMethodName + " then " + comma separated actionMethodsNamesStringBuilder description = new StringBuilder();appendConditionMethodName(description);appendActionMethodsNames(description);org.jeasy.rules.annotation.Rule rule = getRuleAnnotation();this.description = rule.description().equals(Rule.DEFAULT_DESCRIPTION) ? description.toString() : rule.description();}return this.description;}private void appendConditionMethodName(StringBuilder description) {Method method = getConditionMethod();if (method != null) {description.append("when ");description.append(method.getName());description.append(" then ");}}private void appendActionMethodsNames(StringBuilder description) {Iterator<ActionMethodOrderBean> iterator = getActionMethodBeans().iterator();while (iterator.hasNext()) {description.append(iterator.next().getMethod().getName());if (iterator.hasNext()) {description.append(",");}}}public Object getTarget() {return target;}private Class<?> getTargetClass() {return target.getClass();}}
通过实现InvocationHandler完成Interface-based JDK的动态代理过程,且在代理类中,对规则类的注解@Rule、@Condition、@Action、@Fact进行了解析并加以缓存;
3.3 规则引擎调度执行
规则引擎整体类图:
RulesEngine:定义顶层规则引擎接口
AbstractRulesEngine:抽象规则引擎实现类,封装规则拦截器和规则引擎拦截器注册管理逻辑
DefaultRulesEngine:默认规则引擎实现类
InferenceRulesEngine:委托规则引擎实现类,支持条件命中的条件下,多次触发规则执行
RulesEngineParameters:封装规则引擎配置参数
RuleListener:规则执行拦截器器
RulesEngineListener:规则引擎执行拦截器
RulesEngine接口定义如下:
/*** Rules engine interface.** @author Mahmoud Ben Hassine (mahmoud.benhassine@icloud.com)*/
public interface RulesEngine {/*** Return the rules engine parameters.** @return The rules engine parameters*/RulesEngineParameters getParameters();/*** Return the list of registered rule listeners.** @return the list of registered rule listeners*/default List<RuleListener> getRuleListeners() {return Collections.emptyList();}/*** Return the list of registered rules engine listeners.** @return the list of registered rules engine listeners*/default List<RulesEngineListener> getRulesEngineListeners() {return Collections.emptyList();}/*** Fire all registered rules on given facts.*/void fire(Rules rules, Facts facts);/*** Check rules without firing them.* @return a map with the result of evaluation of each rule*/default Map<Rule, Boolean> check(Rules rules, Facts facts) {return Collections.emptyMap();}
}
下面重点分析下fire方法的执行过程:
@Overridepublic void fire(Rules rules, Facts facts) {Objects.requireNonNull(rules, "Rules must not be null");Objects.requireNonNull(facts, "Facts must not be null");triggerListenersBeforeRules(rules, facts);doFire(rules, facts);triggerListenersAfterRules(rules, facts);}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 falseif (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;}}}}
如上fire方法主要的执行步骤为:
1.在doFire方法执行前后,调用了规则引擎拦截器的逻辑
2.在doFire中,遍历执行所有注册的规则
3.在规则执行的前后,也横切了规则拦截器的拦截逻辑
这篇关于EasyRule源码:EasyRule框架源码分析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!