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等;前面的文章也重点分析了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源码:工厂方法模式之规则创建源码分析

2.从实例入手看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!");
    }
}

这里通过注解模式对业务规则进行了定义:

  1. @Rule:表明该类为一个规则类
  2. @Condition:表明规则类中的方法作为规则条件
  3. @Fact:作为条件中的传参
  4. @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.执行过程源码分析

上述的启动类中,主要包含3部分:

1)定义并初始化Facts

2)定义并注册规则

3)定义规则引擎,并进行规则调度

下面分别对这3部分进行分析

3.1 Fact&Facts

Fact&Facts设计目标为EasyRule的传参对象类,源码比较简单易懂,具体如下:

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;
	}

	@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 <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
     */
    @Override
    public Iterator<Fact<?>> iterator() {
        return facts.iterator();
    }

    /**
     * Clear facts.
     */
    public void clear() {
        facts.clear();
    }

    @Override
    public 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
     */
    @Override
    public 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;
    }

    @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<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 upfront
        if (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 actionMethodsNames
            StringBuilder 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();
    }

}

RuleProxy通过实现InvocationHandler完成Interface-based JDK的动态代理过程,且在代理类中,对规则类的注解@Rule、@Condition、@Action、@Fact进行了解析并加以缓存;

3.3 规则引擎调度执行

规则引擎整体类图如下:

RulesEngine:定义顶层规则引擎接口功能

AbstractRulesEngine:抽象规则引擎实现类,封装规则拦截器和规则引擎拦截器注册管理逻辑

DefaultRulesEngine:默认规则引擎实现类

InferenceRulesEngine:委托规则引擎实现类,支持条件命中的条件下,多次触发规则执行

RulesEngineParameters:封装规则引擎配置参数

RuleListener:规则执行拦截器

RulesEngineListener:规则引擎执行拦截器

 这里对RulesEngine接口定义展开如下,规则引擎顶层接口主要完成了

  • 规则拦截器和规则引擎拦截器管理
  • 配置参数管理
  • 调度触发功能(fire方法)
/**
 * 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方法的执行过程:

    @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规则引擎就分析完成了,整体的源码架构也比较简单易懂、逻辑分明、简洁轻量。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值