规则引擎介绍
规则引擎解决的实际上就是判断条件分支过多的问题,举个例子,营销策略里,消费不足200,可用10元消费券,消费超过200元,可以享受9折优惠,超过400元和享受8折优惠。这里可以直接用if else来判断,因为规则比较少,而当判断条件十分复杂之后,使用规则引擎就会能提高系统的可维护性,也能够提高可扩展性。
本文主要介绍规则引擎easy-rule的基本概念和基本使用,同时也会包含使用SpEL表达式作为条件和操作的例子。
基本概念
name:规则命名
description:规则描述
priority:规则的优先级,数字越小,优先级越高
facts:触发规则时的一组已知事实,底层是一个包含多个类似于哈希表fact的set。
conditions:需要满足的条件
actions:满足条件执行的操作
@Rule:定义规则,包括条件(@Condition)和操作(@Action)
RuleBuilder:通过链式结构构件规则
需求实现
我们就以一开始营销的例子,用几种不同的方式实现。
首先引入相关依赖
<dependency>
<groupId>org.jeasy</groupId>
<artifactId>easy-rules-core</artifactId>
<version>4.1.0</version>
</dependency>
<dependency>
<groupId>org.jeasy</groupId>
<artifactId>easy-rules-mvel</artifactId>
<version>4.1.0</version>
</dependency>
<dependency>
<groupId>org.jeasy</groupId>
<artifactId>easy-rules-support</artifactId>
<version>4.1.0</version>
</dependency>
<dependency>
<groupId>org.jeasy</groupId>
<artifactId>easy-rules-spel</artifactId>
<version>4.1.0</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>5.2.5.RELEASE</version>
注解实现
定义了价格小于200时的规则
//该注解表示规则
@Rule
public class CutTenRule {
private Integer price;
// 该注解标记为条件
@Condition
public boolean isLowerThan200(@Fact("price") Integer price) {
this.price = price;
return price < 200;
}
// 该注解表示条件判断之后的操作
@Action
public void printPriceAfterDiscount() {
System.out.println("折扣规则为减10元\n原价为:"+price+"\n折扣后价格的价格为:" + (price > 10 ? price - 10 : price));
}
//该注解表示优先级
@Priority
public int getPriority() {
return 0;
}
}
用接口实现
定义了价格在200~400之间的折扣规则
// 通过接口的方式标记为条件
public class TenPercentDiscountCondition implements Condition {
@Override
public boolean evaluate(Facts facts) {
Integer price = (Integer) facts.get("price");
return price >= 200 && price < 400;
}
}
// 通过接口方式标记为条件运行之后的操作
public class TenPercentDiscountAction implements Action {
@Override
public void execute(Facts facts) throws Exception {
Integer price = facts.get("price");
System.out.println("折扣规则为9折\n原价为:"+price+"\n折扣后的价格为:" + (float)(price * 0.9));
}
}
规则引擎执行
这样我们就实现了两种方式,接下来就是封装这两个规则。
首先,我们介绍一下规则引擎的几个参数。
规则引擎可配置的参数:
- skipOnFirstAppliedRule:当一个规则成功应用时,跳过余下的规则。
- skipOnFirstFailedRule:当一个规则失败时,跳过余下的规则。
- skipOnFirstNonTriggeredRule:当一个规则未触发时,跳过余下的规则。
- rulePriorityThreshold:当优先级超过指定的阈值时,跳过余下的规则。
规则引擎的构造方法:
- DefaultRulesEngine:根据规则的自然顺序(默认为优先级)应用规则。
- InferenceRulesEngine:在已知的事实上不断地应用规则,直到没有更多的规则可用。
public class DiscountClient {
public static void main(String[] args) {
// 创建执行器参数
RulesEngineParameters parameters = new RulesEngineParameters().skipOnFirstAppliedRule(true);
// 创建执行期引擎
DefaultRulesEngine discountEngine = new DefaultRulesEngine(parameters);
// 链式规则完成接口规则的定义
Rule tenPercentRule = new RuleBuilder().name("200~400的折扣")
.description("200~400的折扣")
.when(new TenPercentDiscountCondition())
.then(new TenPercentDiscountAction())
.build();
// 注解实现方式
CutTenRule cutTenRule = new CutTenRule();
// 添加规则
Rules rules = new Rules();
rules.register(tenPercentRule);
rules.register(cutTenRule);
// 定义事实
Facts facts = new Facts();
for (int i = 0; i < 400; i += 9) {
// 添加事实
facts.put("price", i);
// 触发引擎
discountEngine.fire(rules, facts);
}
}
}
运行结果
组合规则
除了自己添加规则之后,easy-rule还提供了组合规则
- UnitRuleGroup:单元规则组是作为一个单元使用的组合规则,要么应用所有规则,要么不应用任何规则。
- ActivationRuleGroup:激活规则组触发第一个适用规则并忽略组中的其他规则。规则首先按照其在组中的自然顺序(默认情况下优先级)进行排序。
- ConditionalRuleGroup:条件规则组将具有最高优先级的规则作为条件,如果具有最高优先级的规则的计算结果为true,那么将触发其余的规则。
以UnitRuleGroup为例:
UnitRuleGroup unitRuleGroup = new UnitRuleGroup("单元规则", "单元规则");
unitRuleGroup.addRule(cutTenRule);
unitRuleGroup.addRule(tenPercentRule);
// 添加规则
Rules rules = new Rules();
rules.register(unitRuleGroup);
按此规则运行上面的引擎,没有任何输出
将之前的规则200这个临界点两个都适用
可以看到在200的地方都执行了
监听器
easy-rule还提供了监听器的的功能,在条件执行前等位置都可以提供监听,只要实现监听器接口即可,这里代码见名知义,不再赘述。
public class MyRuleListener implements RuleListener {
@Override
public boolean beforeEvaluate(Rule rule, Facts facts) {
return RuleListener.super.beforeEvaluate(rule, facts);
}
@Override
public void afterEvaluate(Rule rule, Facts facts, boolean evaluationResult) {
// RuleListener.super.afterEvaluate(rule, facts, evaluationResult);
System.out.println("condition评估之后");
}
@Override
public void beforeExecute(Rule rule, Facts facts) {
System.out.println("action执行之前");
// RuleListener.super.beforeExecute(rule, facts);
}
@Override
public void onSuccess(Rule rule, Facts facts) {
System.out.println("action执行成功");
// RuleListener.super.onSuccess(rule, facts);
}
@Override
public void onFailure(Rule rule, Facts facts, Exception exception) {
System.out.println("action执行失败");
// RuleListener.super.onFailure(rule, facts, exception);
}
}
只需要在执行器中添加即可
// 添加监听器
discountEngine.registerRuleListener(new MyRuleListener());
执行效果:
使用Spel
我们以对象为例实现上述200~400的情况
首先创建商品对象
@Data
@AllArgsConstructor
public class Item {
private int price;
private String expression;
public Item(int price) {
this.price = price;
}
}
创建spel规则
SpELRule spelRule = new SpELRule().name("1")
.description("1")
.priority(1)
.when("#{ ['item'].price >= 400 }")
.then("#{ ['item'].setExpression('折扣规则为8折\n原价为:' + ['item'].price " +
"+ '\n折扣后的价格为:'" +
" + T(java.lang.Float).parseFloat(['item'].price * 0.8+'') ) }");
在rules中注册
rules.register(spelRule);
测试结果
facts.put("item", item);
// 触发引擎
discountEngine.fire(rules, facts);
System.out.println(item.getExpression());
运行结果
执行引擎完整代码:
public class DiscountClient {
public static void main(String[] args) {
// 创建执行器参数
RulesEngineParameters parameters = new RulesEngineParameters().skipOnFirstAppliedRule(true);
// 创建执行期引擎
DefaultRulesEngine discountEngine = new DefaultRulesEngine(parameters);
// 添加监听器
discountEngine.registerRuleListener(new MyRuleListener());
// 链式规则完成接口规则的定义
Rule tenPercentRule = new RuleBuilder().name("200~400的折扣")
.description("200~400的折扣")
.when(new TenPercentDiscountCondition())
.then(new TenPercentDiscountAction())
.build();
//spe方式
SpELRule spelRule = new SpELRule().name("1")
.description("1")
.priority(1)
.when("#{ ['item'].price >= 400 }")
.then("#{ ['item'].setExpression('折扣规则为8折\n原价为:' + ['item'].price " +
"+ '\n折扣后的价格为:'" +
" + T(java.lang.Float).parseFloat(['item'].price * 0.8+'') ) }");
// 注解实现方式
CutTenRule cutTenRule = new CutTenRule();
// 创建组合规则
UnitRuleGroup unitRuleGroup = new UnitRuleGroup("单元规则", "单元规则");
unitRuleGroup.addRule(cutTenRule);
unitRuleGroup.addRule(tenPercentRule);
// 添加规则
Rules rules = new Rules();
rules.register(unitRuleGroup);
rules.register(spelRule);
// 定义事实
Facts facts = new Facts();
for (int i = 0; i < 500; i += 10) {
// 添加事实
facts.put("price", i);
Item item = new Item(i);
facts.put("item", item);
// 触发引擎
discountEngine.fire(rules, facts);
System.out.println(item.getExpression());
}
}
}
总结
执行引擎easy-rule具有强大的解决分支问题的能力,在有多个判断条件时能够使代码的复用性和扩展性提高,同时支持spel等多种表达式,本文主要介绍了简单使用,希望能帮到你~
参考资料
https://juejin.cn/post/7048917724126248967
https://github.com/j-easy/easy-rules
https://zhuanlan.zhihu.com/p/93431125