规则引擎EasyRules_浅析
概念
Easy Rules是一个简单而强大的Java规则引擎,提供以下功能:
- 轻量级框架和易于学习的API
- 基于POJO的开发与注解的编程模型
- 定义抽象的业务规则并轻松应用它们
- 支持从简单规则创建组合规则的能力
- 支持使用表达式语言(如MVEL和SpEL)定义规则的能力
规则对象解释
常用对象:
- 规则参数
- 规则引擎
- 规则<名称、说明、优先级、事实、条件、动作>
规则常用定义:
- 名称(name):规则命名空间中的唯一规则名称
- 说明(description):规则的简要说明
- 优先级(Priority):相对于其他规则的规则优先级
- 事实(Facts):key:value结构的参数,去匹配规则时的一组已知事实
- 条件(Condition):为了匹配该规则,在给定某些事实的情况下应满足的一组条件
- 动作(Action):当条件满足时要执行的一组动作(可以添加/删除/修改事实)
规则对象定义
规则引擎参数
Easy Rules 引擎可以配置以下参数:
Parameter Type Required Default
rulePriorityThreshold int no MaxInt
skipOnFirstAppliedRule boolean no false
skipOnFirstFailedRule boolean no false
skipOnFirstNonTriggeredRule boolean no false
- skipOnFirstAppliedRule:告诉引擎规则被触发时跳过后面的规则。
- skipOnFirstFailedRule:告诉引擎在规则失败时跳过后面的规则。
- skipOnFirstNonTriggeredRule:告诉引擎一个规则不会被触发,就跳过后面的规则。
- rulePriorityThreshold:告诉引擎如果优先级超过定义的阈值,则跳过下一个规则。版本3.3已经不支持更改,默认MaxInt。
可以使用RulesEngineParameters API指定这些参数:
RulesEngineParameters parameters = new RulesEngineParameters()
.rulePriorityThreshold(10)
.skipOnFirstAppliedRule(true)
.skipOnFirstFailedRule(true)
.skipOnFirstNonTriggeredRule(true);
RulesEngine rulesEngine = new DefaultRulesEngine(parameters);
如果要从引擎获取参数,可以使用以下代码段:
RulesEngineParameters parameters = myEngine.getParameters();
这允许您在创建引擎后重置引擎参数。
规则引擎
从版本3.1开始,Easy Rules提供了RulesEngine接口的两种实现:
- DefaultRulesEngine:根据规则的自然顺序(默认为优先级)应用规则。
- InferenceRulesEngine:持续对已知事实应用规则,直到不再应用规则为止。
创建一个规则引擎
要创建规则引擎,可以使用每个实现的构造函数:
RulesEngine rulesEngine = new DefaultRulesEngine();
// or
RulesEngine rulesEngine = new InferenceRulesEngine();
然后,您可以按以下方式触发注册规则:
rulesEngine.fire(rules, facts);
规则(多种定义方式)
1. 使用注解定义规则
@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!");
}
}
2. 用函数方式定义规则
Rule weatherRule = new RuleBuilder()
.name("weather rule")
.description("if it rains then take an umbrella")
.when(facts -> facts.get("rain").equals(true))
.then(facts -> System.out.println("It rains, take an umbrella!"))
.build();
3. 使用表达式方式
Rule weatherRule = new MVELRule()
.name("weather rule")
.description("if it rains then take an umbrella")
.when("rain == true")
.then("System.out.println(\"It rains, take an umbrella!\");");
4. 使用文件描述
weather-rule.yml
name: "weather rule"
description: "if it rains then take an umbrella"
condition: "rain == true"
actions:
- "System.out.println(\"It rains, take an umbrella!\");"
MVELRuleFactory ruleFactory = new MVELRuleFactory(new YamlRuleDefinitionReader());
Rule weatherRule = ruleFactory.createRule(new FileReader("weather-rule.yml"));
传参事实
Facts API是一组事实的抽象,在这些事实上检查规则。在内部,Facts实例持有HashMap<String,Object>,这意味着:
- 事实需要命名,应该有一个唯一的名称,且不能为空
- 任何Java对象都可以充当事实
这里有一个实例定义事实:
Facts facts = new Facts();
facts.add("rain", true);
Facts 能够被注入规则条件,action 方法使用 @Fact
注解. 在下面的规则中,rain
事实被注入itRains方法的rain
参数:
@Rule
class WeatherRule {
@Condition
public boolean itRains(@Fact("rain") boolean rain) {
return rain;
}
@Action
public void takeAnUmbrella(Facts facts) {
System.out.println("It rains, take an umbrella!");
// can add/remove/modify facts
}
}
Facts
类型参数 被注入已知的 facts中 (像action方法takeAnUmbrella
一样).
如果缺少注入的fact, 这个引擎会抛出 RuntimeException
异常.
如何使用
运行环境
Easy Rules是一个Java库, 需要运行在Java 1.7及以上。
maven依赖
<!--easy rules核心库-->
<dependency>
<groupId>org.jeasy</groupId>
<artifactId>easy-rules-core</artifactId>
<version>4.1.0</version>
</dependency>
<!--规则定义文件格式,支持json,yaml等-->
<dependency>
<groupId>org.jeasy</groupId>
<artifactId>easy-rules-support</artifactId>
<version>4.1.0</version>
</dependency>
<!--支持mvel规则语法库-->
<dependency>
<groupId>org.jeasy</groupId>
<artifactId>easy-rules-mvel</artifactId>
<version>4.1.0</version>
</dependency>
代码示例
package com.rule.domain;
import org.jeasy.rules.annotation.Action;
import org.jeasy.rules.annotation.Condition;
import org.jeasy.rules.annotation.Fact;
import org.jeasy.rules.annotation.Rule;
public class DigitalRule {
@Rule(name = "fizzRule", description = "能否被5整除", priority = 1)
public static class FizzRule {
@Condition
public boolean isFizz(@Fact("input") int input) {
return input % 5 == 0;
}
@Action
public void printFizz(@Fact("input") int input) {
System.out.println(input + ":能被5整除(fizz)");
}
}
@Rule(name = "buzzRule", description = "能否被7整除", priority = 2)
public static class BuzzRule {
@Condition
public boolean isBuzz(@Fact("input") int input) {
return input % 7 == 0;
}
@Action
public void printBuzz(@Fact("input") int input) {
System.out.println(input + ":能被7整除(buzz)");
}
}
@Rule(name = "nonFizzBuzzRule", description = "不能被5整除 或 不能被7整除", priority = 3)
public static class NonFizzOrBuzzRule {
@Condition
public boolean isNotFizzOrBuzz(@Fact("input") int input) {
return input % 5 != 0 || input % 7 != 0;
}
@Action
public void printInput(@Fact("input") int input) {
System.out.println(input + ":不能被5整除 或 不能被7整除");
}
}
@Rule(name = "nonFizzBuzzRule", description = "不能被5整除 且 不能被7整除", priority = 4)
public static class NonFizzAndBuzzRule {
@Condition
public boolean isNotFizzAndBuzz(@Fact("input") int input) {
return input % 5 != 0 && input % 7 != 0;
}
@Action
public void printInput(@Fact("input") int input) {
System.out.println(input + ":不能被5整除 且 不能被7整除");
}
}
}
package com.rule.main;
import com.rule.domain.DigitalRule;
import org.jeasy.rules.api.*;
import org.jeasy.rules.core.DefaultRulesEngine;
public class FizzBuzzWithEasyRules {
public static void main(String[] args) {
RulesEngineParameters parameters = new RulesEngineParameters()
.skipOnFirstAppliedRule(false)//告诉引擎规则被触发时跳过后面的规则。
.skipOnFirstFailedRule(false)//告诉引擎在规则失败时跳过后面的规则。
.skipOnFirstNonTriggeredRule(false);//告诉引擎一个规则不被触发,就跳过后面的规则。
RulesEngine fizzBuzzEngine = new DefaultRulesEngine(parameters);
// register rules
Rules rules = new Rules();
rules.register(new DigitalRule.FizzRule());
rules.register(new DigitalRule.BuzzRule());
rules.register(new DigitalRule.NonFizzAndBuzzRule());
rules.register(new DigitalRule.NonFizzOrBuzzRule());
// fire rules
Facts facts = new Facts();
for (int i = 0; i <= 100; i++) {
facts.put("input", i);
fizzBuzzEngine.fire(rules, facts);
}
}
}
源码解析
RuleEngine的构造就是new了一个实现,facts就是一个Map的参数结构。我们主要看register方法注册规则,以及fire方法触发规则判断的逻辑
注册rule
我们跟进Rules的register方法
private Set<Rule> rules = new TreeSet<>();
public void register(Object rule) {
Objects.requireNonNull(rule);
rules.add(RuleProxy.asRule(rule));
}
RuleProxy.asRule将我们传入的原始对象做了一层动态代理,然后直接添加到rules的集合当中。
很明显,后面的fire会遍历该rules。register方法的核心实现就落在了RuleProxy.asRule上了
我们跟进asRule方法
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;
}
asRule方法先判断了一下rule是否实现了Rule接口,如果按照接口的实现那么就不需要动态代理,直接返回。
那么没有实现rule的接口呢?
validateRuleDefinition将会进行校验判断是否符合一个rule的定义,如果不符合则抛出异常。
如果符合rule的定义,那么通过JDK的动态代理获取一个实现了Rule接口和Comparable接口的代理对象,被代理对象就是当前rule。
validateRuleDefinition
那么validateRuleDefinition是怎么判断是否符合rule的呢?
void validateRuleDefinition(final Object rule) {
checkRuleClass(rule);
checkConditionMethod(rule);
checkActionMethods(rule);
checkPriorityMethod(rule);
}
该方法分别校验了
1)是否rule类,需要被@Rule注解
2)是否有Condition条件,Condition有且只有1个。且方法必须是public修饰,返回boolean值。方法的参数必须有@Fact来修饰
3)是否有action方法,action方法数量大于等于1个。且方法必须是public修饰,返回值是void,方法的参数必须有@Facts来修饰
4)是否有priority优先级方法,priority方法可以没有,但是有的话只能有一个。且被public修饰,返回int值,同时不能有参数
fire方法触发规则逻辑
前面的规则注册,将会获取一个实现了Rule接口的rule对象。不管是自己实现的对象还是由JDK动态代理实现的对象。
跟进DefaultRulesEngine的fire方法
@Override
public void fire(Rules rules, Facts facts) {
triggerListenersBeforeRules(rules, facts);
doFire(rules, facts);
triggerListenersAfterRules(rules, facts);
}
doFire前后触发了监听器,跟进doFire
doFire方法比较长,我们首先看到的是对Rules进行遍历。每个Rule先触发evaluate方法,如果evaluate返回true则进行execute方法执行。
evaluate 等同于Condition判断
execute 等同于触发action
void doFire(Rules rules, Facts facts) {
for (Rule rule : rules) {
final String name = rule.getName();
final int priority = rule.getPriority();
if (priority > parameters.getPriorityThreshold()) {
break;
}
if (!shouldBeEvaluated(rule, facts)) {
continue;
}
if (rule.evaluate(facts)) {
triggerListenersAfterEvaluate(rule, facts, true);
try {
triggerListenersBeforeExecute(rule, facts);
rule.execute(facts);
triggerListenersOnSuccess(rule, facts);
if (parameters.isSkipOnFirstAppliedRule()) {
break;
}
} catch (Exception exception) {
triggerListenersOnFailure(rule, exception, facts);
if (parameters.isSkipOnFirstFailedRule()) {
break;
}
}
} else {
triggerListenersAfterEvaluate(rule, facts, false);
if (parameters.isSkipOnFirstNonTriggeredRule()) {
break;
}
}
}
}
evaluate条件判断
我们以代理对象为例,跟进evaluate方法。打开RuleProxy,我们看看invoke方法
@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;
}
}
可以看到,每一个被动态代理的对象将会被拦截evaluate方法。然后交付给evaluateMethod执行,我们跟进evaluateMethod方法
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());
} catch (NoSuchFactException e) {
return false;
} catch (IllegalArgumentException e) {
// ...
}
}
getConditionMethod将会反射获取到该rule定义了@Condition的Method,然后遍历该Method的所有@Facts参数,从Facts中获取所有参数值形成一个array。
拿到Method和参数以后,直接反射触发该方法,返回一个boolean对象值
execute执行action
如果evaluate返回值为true,也就是Condition满足了。那么就会触发execute方法,action将会被调用。
实现逻辑和evaluate类似,我们跟进RuleProxy的executeMethod
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;
}
和evaluate类似,先是获取了rule中@Action的方法。然后获取@Fact参数值,最后返回执行,不需要返回结果。
总结
- 简单的条件语句,还是使用if/else快捷。
- 复杂的业务逻辑,例如判断不同业务规则交织时候,适合使用EasyRules,观看方便明了。
- 方便维护,以及后期修改。
注意点
-
Condition条件有且只有1个。且方法必须是public修饰,返回boolean值
-
action方法数量大于等于1个,且方法必须是public修饰,返回值是void。
-
priority方法可以没有,但是有的话只能有一个。且被public修饰,返回int值,同时不能有参数。
-
Easy Rules现在处于维护状态,最好的受支持版本为4.1.x,最好使用此版本。