easy-rules系列——Core源码分析
easy-rules仓库地址,开源协议是MIT。
本文是基于easy-rules的4.0.0版本。
作者:weiyiysw
邮箱:weiyiysw@163.com
本文章采用CC BY-NC-ND 4.0 许可协议进行许可
easy-rules是什么
Easy Rules是一个Java语言的规则引擎,由Martin Fowler的一篇文章Should I use a Rules Engine?启发而完成的。Martin说
You can build a simple rules engine yourself. All you need is to create a bunch of objects with conditions and actions, store them in a collection, and run through them to evaluate the conditions and execute the actions.
Easy Rules就是基于此诞生的。Easy Rules的源码相当简单,并且对于理解规则引擎也是很有帮助。
Easy Rules的核心特性包括:
API简单,轻量级的库
可以基于POJO开发,以及annotation的编程模型
具备从基本规则创建合成复合规则
支持SpEL和MVEL
easy-rules源码导读
我们先来看下easy-rules的项目的整体结构
➜ easy-rules git:(master) tree -L 1
.
├── easy-rules-archetype
├── easy-rules-core
├── easy-rules-mvel
├── easy-rules-spel
├── easy-rules-support
├── easy-rules-tutorials
从以上项目结构中可以看到,archetype是maven的骨架,可基于这个生成基础的项目。tutorials是使用demo,如果只关心如何使用,查看这个项目即可。
阅读源码,那么我们需要关注的是
easy-rules-core:规则引擎的核心逻辑
easy-rules-mvel:提供MVEL的支持
easy-rules-spel:提供SpELl的支持
easy-rules-support:将基本规则组合成复合规则
easy-rules-core源码
core的源码分为3个package
annotation:通知,可基于通知生成规则
api:定义了规则、事实接口
core:主要是规则引擎的实现
规则引擎core分析
规则引擎的三要素:事实对象、规则逻辑、规则动作。
规则引擎启动后,基于现有的事实对象,进行逻辑判断,当逻辑判断为true
时,则执行规则动作,为false
时,不执行规则动作。
规则引擎关心的是处理过程中的复杂条件组合,能做到动态的修改规则逻辑而无需更改任何代码。
annotation
annotation包里有5个通知,分别是Action、Condition、Fact、Priority、Rule。这5个通知是用来定义规则及事实的。
api
规则接口:Action、Condition、Rule。
通过规则接口实现规则的定义。
Rule接口具有优先级,默认的情况下,数字越小代表优先级越高。Rule接口继承Comparable接口,支持大家自定义自己的规则优先级。一般来说,默认的即满足使用。
规则监听器接口:RuleListener、RulesEngineListener。
RuleListener
此接口和规则相关,规则执行的监听器,用以“生命周期”各个阶段类比,可包含以下阶段,并在各个阶段进行自定义监听。
各个阶段包括:规则触发前、规则触发后、动作执行前、动作执行后、触发并执行成功、触发并执行失败、规则逻辑触发失败。
RulesEngineListener
顾名思意,规则引擎的监听器。可监听的阶段为:规则执行之前、规则执行之后。
规则引擎接口:RuleEngine
如果觉得内置的规则引擎不合适,可实现此接口,自定义规则引擎。
Fact与Facts
Fact类是事实对象的泛型类,一个Fact对象对应一个事实对象。
Facts是Fact事实对象的集合,内部实现为Set集合。
Rules
Rules类是规则的集合,内部以TreeSet存储。前面有说到,规则包含属性Priority
,数字越小优先级越高。
我们制定的规则,需要注册到Rules对象中。也可以从Rules对象中移除规则。
规则引擎参数
目前有4个规则引擎参数
skipOnFirstAppliedRule
skipOnFirstNonTriggeredRule
skipOnFirstFailedRule
prioityThreshold
3个skip的boolean参数,相信大家看名字也能知道其作用。最后一个优先级阈值的参数是int
类型,其作用是跳过超过用户定义的优先级阈值的规则。默认的情况下,这个参数值是Integer.MAX_VALUE
。比如,在规则引擎里,我们设置prioityThreshold = 10
,那么,规则集合里(Rules)所有优先级大于10的都会被跳过,不会执行。
在DefaultRulesEngine
中,规则参数会应用到所有的规则,且prioityThreshold = 10
的情况下,优先级小于等于10的所有规则都会执行。
在InferenceRulesEngine
中,规则参数只会应用到候选(candidate)规则上,且prioityThreshold = 10
的情况下,优先级小于等于10的所有规则都会执行。那么,什么是候选规则呢?在这个规则引擎中,规则集(Rules)里会对所有规则进行逻辑判断,当逻辑判断结果为true
时,会进入到候选规则上。对应的源码如下:
private Set<Rule> selectCandidates(Rules rules, Facts facts) {
Set<Rule> candidates = new TreeSet<>();
for (Rule rule : rules) {
if (rule.evaluate(facts)) {
candidates.add(rule);
}
}
return candidates;
}
core
core包中的源码,重点看规则引擎(RuleEngine)的源码以及规则代理(RuleProxy)。
规则引擎
我们先来看下规则引擎的类图。

如上,easy-rules实现了2个规则引擎,DefaultRulesEngine
与InferenceRulesEngine
。
先来看默认的规则引擎,关注2个方法,fire
方法以及内置的doFire
方法。
@Override
public void fire(Rules rules, Facts facts) {
triggerListenersBeforeRules(rules, facts);
doFire(rules, facts);
triggerListenersAfterRules(rules, facts);
}
可以看到,fire
方法中,前后执行RuleEngineListener
的接口。真正执行的逻辑在doFire
中。
接下来,我们看一下doFire
逻辑
代码很长,部分会省略,省略的以 ... 表示
void doFire(Rules rules, Facts facts) {
// check rules is not empty
//...
// log engineParameters rules facts
//...
for (Rule rule : rules) {
// 判断规则的优先级与规则引擎的参数优先级
//
// ...
boolean evaluationResult = false;
try {
// 根据事实,检查规则的逻辑条件是否为true
evaluationResult = rule.evaluate(facts);
} catch() {
//...
}
// 规则的逻辑条件为true
if (evaluationResult) {
//...
// 执行规则
rule.execute(facts);
//...
}
}
}
以上,就是doFire
的核心逻辑了。
省略的部分都是RuleListener
的接口,以便你可以在规则执行的过程中,进行监听,并进行加入自己的代码逻辑。




