目录
1.简要介绍EasyRule
2.从实例入手看EasyRule使用方法
3.执行过程源码分析
3.1 Fact&Facts
3.2 规则定义和注册
3.2.1 Rule接口
3.2.2 规则注册管理
3.2.3 创建规则代理
3.3 规则引擎调度执行
当下的规则引擎选择非常多,例如 EasyRule、Aviator 、QLExpress、Drools等;前面的文章也重点分析了Aviator框架源码,相关文章见:
1.【精选】Aviator源码:从具体实例看Aviator属性语法糖源码分析(a.b.c)
2. Aviator源码:Aviator表达式引擎执行过程源码分析
本篇对另一种常用的规则引擎框架EasyRule进行源码分析;
EasyRule作为一种小而美的轻量级的规则引擎开源框架,从实际业务规则应用场景出发,抽象出了条件(Condition)、动作(Action)、规则(Rule)数据模型,并通过规则引擎(RulesEngine)完成规则的调度执行。
EasyRule的主要特性归纳如下:
轻量级框架和易于学习的API
基于POJO的开发与注解的编程模型
定义抽象的业务规则并轻松应用它们
支持从简单规则创建组合规则的能力
支持使用表达式语言(如MVEL和SpEL)定义规则的能力
本文对EasyRule归纳性的描述不再过多赘述,下面主要着重对EasyRule框架源码进行分析;
附:EasyRule引擎规则创建过程的源码分析详见:
EasyRule源码:工厂方法模式之规则创建源码分析
EasyRule源码大家可以在github上找到:Github之EasyRule
这里选取源码中Tutorial模块中的例子进行说明:
@Rule(name = "weather rule", description = "if it rains then take an umbrella")
public class WeatherRule {
@Condition
public boolean itRains(@Fact("rain") boolean rain) {
return rain;
}
@Action
public void takeAnUmbrella() {
System.out.println("It rains, take an umbrella!");
}
}
这里通过注解模式对业务规则进行了定义:
- @Rule:表明该类为一个规则类
- @Condition:表明规则类中的方法作为规则条件
- @Fact:作为条件中的传参
- @Action:作为规则条件命中之后具体执行的动作
完成规则类的定义后,通过启动测试验证类,验证规则的执行情况:
public class Launcher {
public static void main(String[] args) {
// define facts
Facts facts = new Facts();
facts.put("rain", true);
// define rules
WeatherRule weatherRule = new WeatherRule();
Rules rules = new Rules();
rules.register(weatherRule);
// fire rules on known facts
RulesEngine rulesEngine = new DefaultRulesEngine();
rulesEngine.fire(rules, facts);
}
}
上述的启动类中,主要包含3部分:
1)定义并初始化Facts
2)定义并注册规则
3)定义规则引擎,并进行规则调度
下面分别对这3部分进行分析
Fact&Facts设计目标为EasyRule的传参对象类,源码比较简单易懂,具体如下:
public class Fact {
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;
}
@Override
public 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.
*/
@Override
public 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);
}
@Override
public int hashCode() {
return Objects.hash(name);
}
}
/**
* This class encapsulates a set of facts and represents a facts namespace.
* Facts have unique names within a Facts
object.
*
* @author Mahmoud Ben Hassine ([email protected])
*/
public class Facts implements Iterable> {
private final Set> 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 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 void add(Fact 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 void remove(Fact 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 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 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 asMap() {
Map 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
*/
@Override
public Iterator> iterator() {
return facts.iterator();
}
/**
* Clear facts.
*/
public void clear() {
facts.clear();
}
@Override
public String toString() {
Iterator> 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集合,以及对应的集合操作
规则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 unique name.
*
* @author Mahmoud Ben Hassine ([email protected])
*/
public interface Rule extends Comparable {
/**
* 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).
* Implementations should handle any runtime exception and return true/false accordingly
*
* @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;
}
规则类定义完成后,需要对规则进行注册;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 ([email protected])
*/
public class Rules implements Iterable {
private Set rules = new TreeSet<>();
/**
* Create a new {@link Rules} object.
*
* @param rules to register
*/
public Rules(Set 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
*/
@Override
public Iterator iterator() {
return rules.iterator();
}
private Rule findRuleByName(String ruleName) {
return rules.stream()
.filter(rule -> rule.getName().equalsIgnoreCase(ruleName))
.findFirst()
.orElse(null);
}
}
在上面规则注册的方法中,通过调用RuleProxy.asRule(rule)完成规则的代理,实际注册的是规则的代理类,下面重点剖析下代理类的构造过程:
/**
* Main class to create rule proxies from annotated objects.
*
* @author Mahmoud Ben Hassine ([email protected])
*/
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 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;
}
@Override
public 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
RuleProxy通过实现InvocationHandler完成Interface-based JDK的动态代理过程,且在代理类中,对规则类的注解@Rule、@Condition、@Action、@Fact进行了解析并加以缓存;
规则引擎整体类图如下:
RulesEngine:定义顶层规则引擎接口功能
AbstractRulesEngine:抽象规则引擎实现类,封装规则拦截器和规则引擎拦截器注册管理逻辑
DefaultRulesEngine:默认规则引擎实现类
InferenceRulesEngine:委托规则引擎实现类,支持条件命中的条件下,多次触发规则执行
RulesEngineParameters:封装规则引擎配置参数
RuleListener:规则执行拦截器
RulesEngineListener:规则引擎执行拦截器
这里对RulesEngine接口定义展开如下,规则引擎顶层接口主要完成了
/**
* Rules engine interface.
*
* @author Mahmoud Ben Hassine ([email protected])
*/
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 getRuleListeners() {
return Collections.emptyList();
}
/**
* Return the list of registered rules engine listeners.
*
* @return the list of registered rules engine listeners
*/
default List 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 check(Rules rules, Facts facts) {
return Collections.emptyMap();
}
}
下面重点分析下fire方法的执行过程:
@Override
public 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 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;
}
}
}
}
如上fire方法主要的执行步骤为:
1.在doFire方法执行前后,调用了规则引擎拦截器的逻辑
2.在doFire中,遍历执行所有注册的规则(执行命中条件evaluate以及命中后的执行方法execute)
3.在规则执行的前后,也横切了规则拦截器的拦截逻辑
至此,一个简单的EasyRule规则引擎就分析完成了,整体的源码架构也比较简单易懂、逻辑分明、简洁轻量。