Java规则引擎easy-rules
首先以通俗的语言表达何为规则引擎:
一段代码:
public class FizzBuzz {
public static void main(String[] args) {
for(int i = 1; i <= 100; i++) {
if (((i % 5) == 0) && ((i % 7) == 0)){
System.out.print("能被5和7同时整除的数字");
}
else {
System.out.print("i");
}
}
}
}
上述代码中循环了100次,从1开始循环+1.
如果当前数字能被5、7整除。打印:能被5和7同时整除的数字
其余的数字依次打印出来。
其实上面例子中的如果就是我们希望的规则,由此可见当我们业务上遇到较为复杂多样的规则的时候,代码中会出现满篇的if–else。降低代码的易读性、增加代码行数,后期修改规则繁复等等的弊端都体现出来。因此我们用到了规则引擎,也就是今天的主人公——easy-rules。
Easy Rules是一个简单但功能强大的Java规则引擎,提供以下特性:
- 轻量级框架和易于学习的API
- 基于POJO的开发
- 支持从原始规则创建组合规则
- 支持通过表达式(如MVEL,SPEL和JEXL)定义规则
先从一段源码来了解:
package org.jeasy.rules.api;
public interface Rule extends Comparable<Rule> {
private final Condition condition;
private final List<Action> actions;
//name:规则命名(空间中的唯一规则名称)
default String getName() {
return "rule";
}
//规则的简要描述
default String getDescription() {
return "description";
}
//规则的优先级 1>2>3>4..以此类推
default int getPriority() {
return 2147483646;
}
/**
* 此方法封装了规则的条件。
* @return 如果根据提供的事实可以应用规则,则为true,否则为false
*/
public boolean evaluate(Facts facts) {
return this.condition.evaluate(facts);
}
/**
* 此方法封装了规则的操作。
* @throws 如果在执行操作期间发生错误,则抛出异常
*/
public void execute(Facts facts) throws Exception {
Iterator var2 = this.actions.iterator();
while(var2.hasNext()) {
Action action = (Action)var2.next();
action.execute(facts);
}
}
}
evaluate()方法封装了必须为true才能触发规则的条件。
execute()方法封装了在满足规则条件时应该执行的操作。
条件和操作由Condition和Action接口表示。
总结来说,我们可以通过定义Condition来判断是否执行此规则,通过Action来决定执行什么样的规则。
上面大概讲述了一下rule的基本结构,下面我们来探讨下究竟如何使用:
注解方式:
完整的示例需要几个东西要讲一下,大家耐心看完:
- 第一点:Easy Rules提供了
@Rule
注解,可以将POJO转换为规则
@Rule(name = "myrule", description = "这是一个规则的描述", priority = 1)
public class MyRule {
// @Condition注解用来标记是否符合规则的方法,这个方法必须是public;
//可以有一个或多个带@Fact注解的参数,并返回一个boolean类型。
//只有一个方法可以用@Condition注解标记。
@Condition
public boolean when(@Fact("fact") fact) {
return true;
}
//@Action注解用来标记执行操作的方法,规则可以有多个操作。可以使用order属性以指定的顺序执行操作
@Action(order = 1)
public void then(Facts facts) throws Exception {
// 规则为true时的操作1
}
@Action(order = 2)
public void finally() throws Exception {
// 规则为true时的操作2
}
}
上面代码中,我们自己通过注解的方式先定义了一个规则,以及规则是否执行的条件,执行的内容。
- 第二点:上面代码中的
@Fact
注解是什么意思呢?
源码:
public class Fact<T> {
private final String name;
private final T value;
}
实际上是一个事实,name代表了在事实中的命名,value则是实际要传递的参数。
Facts API表示一组事实 所以在同一空间内,fact的name不可重复。
//新建一组事实
Facts facts = new Facts();
facts.put("isFive", "5");
- 第三点:建立规则引擎
//引擎被创建 默认的应用规则
RulesEngine rulesEngine = new DefaultRulesEngine();
// or 自定义应用规则
RulesEngine rulesEngine = new InferenceRulesEngine();
//执行。
rulesEngine.fire(rules, facts);
规则引擎参数
Easy Rules引擎可以配置以下参数:
-
rulePriorityThreshold
:当优先级超过指定的阈值时,跳过余下的规则。 -
skipOnFirstAppliedRule
:当一个规则成功应用时,跳过余下的规则。 -
skipOnFirstFailedRule
:当一个规则失败时,跳过余下的规则。 -
skipOnFirstNonTriggeredRule
:当一个规则未触发时,跳过余下的规则。
示例:
RulesEngineParameters parameters = new RulesEngineParameters()
.rulePriorityThreshold(10)
.skipOnFirstAppliedRule(true)
.skipOnFirstFailedRule(true)
.skipOnFirstNonTriggeredRule(true);
RulesEngine rulesEngine = new DefaultRulesEngine(parameters);
简单了解了以上三点,便可以进行一个简单的程序了:
按照我们开篇的例子来说
循环100次,从1开始循环+1.
如果当前数字能被5、7整除。打印:能被5和7同时整除的数字
其余的数字依次打印出来。
把上述规则建立起来 ↓
/**
当前数字能被5、7整除。打印:能被5和7同时整除的数字
**/
@Rule
public class FiveSevenRule {
@Condition
public boolean isFiveSevenRule (@Fact("number") Integer number) {
return number % 5 == 0 && number % 7 == 0;
}
@Action
public void printInput(@Fact("number") Integer number) {
System.out.print("能被5和7同时整除的数字");
}
@Priority
public int getPriority() {
return 1;
}
}
/**
打印其余数字
**/
@Rule
public class OrtherNumRule {
@Condition
public boolean isFiveSevenRule (@Fact("number") Integer number) {
return true;
}
@Action
public void printInput(@Fact("number") Integer number) {
System.out.print(number);
}
@Priority
public int getPriority() {
return 2;
}
}
使用:
public class FizzBuzzWithEasyRules {
public static void main(String[] args) {
// 创建规则引擎
RulesEngineParameters parameters = new RulesEngineParameters().skipOnFirstAppliedRule(true);
RulesEngine fizzBuzzEngine = new DefaultRulesEngine(parameters);
// 创建规则
Rules rules = new Rules();
rules.register(new FiveSevenRule());
rules.register(new OrtherNumRule());
// 触发规则
Facts facts = new Facts();
for (int i = 1; i <= 100; i++) {
facts.put("number", i);
fizzBuzzEngine.fire(rules, facts);
}
}
}
结果:
由此可见,我们的规则运行起来了。注意:我在创建规则引擎的时候,用了.skipOnFirstAppliedRule(true);
也就是说按规则顺序执行后,当规则1完成后,跳过规则2。所以34后面不会再打印35,而直接接了36.
以上便是简单的规则使用。
未完待续…