官网源码:https://github.com/j-easy/easy-rules
官网案例:https://github.com/j-easy/easy-rules/wiki/fizz-buzz
介绍:
规则引擎是为了解决业务代码和业务规则分离的引擎,是一种嵌入在应用程序中的组件,实现了将业务决策从应用程序代码中分离,其实就是将一大堆if/else进行处理,Easy Rules 所做的,它提供了Rule创建具有条件和操作的规则的抽象,以及RulesEngine通过一组规则运行以评估条件和执行操作的API
核心功能:
- 轻量级库和易于学习的 API
- 带有注解编程模型的基于 POJO 的开发
- 用于定义业务规则并通过 Java 轻松应用它们的有用抽象
- 从原始规则创建复合规则的能力
- 使用表达式语言(如 MVEL、SpEL 和 JEXL)定义规则的能力
一个规则由名称、描述、优先级三个属性和判断、执行两个方法组成,实现Rule接口,
和使用@Rule,@Condition,@Action,@Priority,@Fact注解的效果是一样的。
它主要包括几个主要的类或接口:Rule
,RulesEngine
,RuleListener
,Facts
还有几个主要的注解:@Action
,@Condition
,@Fact
,@Priority
,@Rule
- Rule
public interface Rule extends Comparable<Rule> {
/**
* 默认的规则名称
*/
String DEFAULT_NAME = "rule";
/**
* 默认的规则描述
*/
String DEFAULT_DESCRIPTION = "description";
/**
* 默认的规则优先级
*/
int DEFAULT_PRIORITY = Integer.MAX_VALUE - 1;
getter and setter...
/**
* 规则引擎判断条件
* 如果提供的facts被应用到规则上返回true,否则返回false
*/
boolean evaluate(Facts facts);
/**
* 规则引擎判断条件返回true后,执行此方法
*/
void execute(Facts facts) throws Exception;
}
- RulesEngine负责检查和开启规则,同时可以得到规则引擎的参数和规则监听器列表
public interface RulesEngine {
/**
* 返回规则引擎的参数
*/
RulesEngineParameters getParameters();
/**
* 返回已注册的规则监听器的列表
*/
List<RuleListener> getRuleListeners();
/**
* 在给定的因素上开启所有已注册的规则
*/
void fire(Rules rules, Facts facts);
/**
* 检查规则和因素是否符合
*/
Map<Rule, Boolean> check(Rules rules, Facts facts);
}
- RuleListener在规则执行的4个阶段加上了触发器,可以灵活地控制规则执行结果
(定义规则监听器通过实现RuleListener接口)
public interface RuleListener {
/**
* 规则条件判断之前的触发器
*/
boolean beforeEvaluate(Rule rule, Facts facts);
/**
* 规则条件判断之后的触发器
*/
void afterEvaluate(Rule rule, Facts facts, boolean evaluationResult);
/**
* 规则执行之前的触发器
*/
void beforeExecute(Rule rule, Facts facts);
/**
* 规则执行成功之后的触发器
*/
void onSuccess(Rule rule, Facts facts);
/**
* 规则执行失败之后的触发器
*/
void onFailure(Rule rule, Facts facts, Exception exception);
}
- Facts就是一个hashmap,通过注解@Fact(String value),其中的value是map的key,可以拿到Facts中的value
public class Facts implements Iterable<Map.Entry<String, Object>> {
private Map<String, Object> facts = new HashMap<>();
/**
* 在工作空间放置一个因素
*/
public Object put(String name, Object fact) {
Objects.requireNonNull(name);
return facts.put(name, fact);
}
/**
* 删除因素
*/
public Object remove(String name) {
Objects.requireNonNull(name);
return facts.remove(name);
}
/**
* 通过name得到因素
*/
public Object get(String name) {
Objects.requireNonNull(name);
return facts.get(name);
}
/**
* 以map形式返回因素
*/
public Map<String, Object> asMap() {
return facts;
}
...
}
一. pom.xml中引入maven依赖
<!--easy rules核心库-->
<dependency>
<groupId>org.jeasy</groupId>
<artifactId>easy-rules-core</artifactId>
<version>4.0.0</version>
</dependency>
<!--规则定义文件格式,支持json,yaml等-->
<dependency>
<groupId>org.jeasy</groupId>
<artifactId>easy-rules-support</artifactId>
<version>4.0.0</version>
</dependency>
<!--支持mvel规则语法库-->
<dependency>
<groupId>org.jeasy</groupId>
<artifactId>easy-rules-mvel</artifactId>
<version>4.0.0</version>
</dependency>
二. 定义规则
大多数业务规则可以由以下定义表示:
- 名称:规则命名空间中的唯一规则名称
- 说明:规则的简要说明
- 优先级:相对于其他规则的规则优先级
- 事实:去匹配规则时的一组已知事实
- 条件:为了匹配该规则,在给定某些事实的情况下应满足的一组条件
- 动作:当条件满足时要执行的一组动作(可以添加/删除/修改事实)
Easy Rules为定义业务规则的每个关键点提供了抽象。
public interface Rule {
/**
* 改方法封装规则的条件(conditions)
* @return 如果提供的事实适用于该规则返回true, 否则,返回false
*/
boolean evaluate(Facts facts);
/**
* 改方法封装规则的操作(actions)
* @throws 如果在执行过程中发生错误将抛出Exception
*/
void execute(Facts facts) throws Exception;
//Getters and setters for rule name, description and priority omitted.
}
evaluate方法封装了必须求值为TRUE才能触发规则的条件。
execute方法封装了在满足规则条件时应执行的操作。条件和动作ConditionandAction接口表示。
首先,定义规则,方式有多种
方式一:注解
- @Condition注解指定规则条件
- @Fact注解指定参数
- @Action注解指定规则执行的动作
@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!");
}
}
@Condition注解标记计算规则条件的方法。此方法必须是公共的,可以有一个或多个用@Fact注解的参数,并返回布尔类型。只有一个方法能用@Condition注解。
@Action注解标记要执行规则操作的方法。规则可以有多个操作。可以使用order属性按指定的顺序执行操作。默认情况下,操作的顺序为0。
方式二:链式编程
Rule rule = new RuleBuilder()
.name("myRule")
.description("myRuleDescription")
.priority(3)
.when(condition)
.then(action1)
.then(action2)
.build();
在这个例子中, Condition实例condition,Action实例是action1和action2。
方式三:表达式
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!\");");
方式四:yml配置文件
例如: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"));
组合规则
CompositeRule由一组规则组成。这是一个典型地组合设计模式的实现。
组合规则是一个抽象概念,因为可以以不同方式触发组合规则。
Easy Rules自带三种CompositeRule实现:
UnitRuleGroup : 要么应用所有规则,要么不应用任何规则(AND逻辑)
ActivationRuleGroup : 它触发第一个适用规则,并忽略组中的其他规则(XOR逻辑)
ConditionalRuleGroup : 如果具有最高优先级的规则计算结果为true,则触发其余规则
复合规则可以从基本规则创建并注册为常规规则:
//Create a composite rule from two primitive rules
UnitRuleGroup myUnitRuleGroup = new UnitRuleGroup("myUnitRuleGroup", "unit of myRule1 and myRule2");
myUnitRuleGroup.addRule(myRule1);
myUnitRuleGroup.addRule(myRule2);
//Register the composite rule as a regular rule
Rules rules = new Rules();
rules.register(myUnitRuleGroup);
RulesEngine rulesEngine = new DefaultRulesEngine();
rulesEngine.fire(rules, someFacts);
每个规则都有优先级。它代表触发注册规则的默认顺序。默认情况下,较低的值表示较高的优先级。可以重写compareTo方法以提供自定义优先级策略。
定义事实
Facts API是一组事实的抽象,在这些事实上检查规则。
在内部,Facts实例持有HashMap<String,Object>,这意味着:
事实需要命名,应该有一个唯一的名称,且不能为空
任何Java对象都可以充当事实
这里有一个实例定义事实:
// define facts
Facts facts = new Facts();
facts.add("rain", true);
用@Fact注解可以将Facts注入到condition和action方法中
在下面的规则中,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异常.
定义规则引擎
从版本3.1开始,Easy Rules提供了RulesEngine接口的两种实现:
- DefaultRulesEngine:根据规则的自然顺序(默认为优先级)应用规则。
- InferenceRulesEngine:持续对已知事实应用规则,直到不再应用规则为止。
创建一个规则引擎
要创建规则引擎,可以使用每个实现的构造函数:
RulesEngine rulesEngine = new DefaultRulesEngine();
// or
RulesEngine rulesEngine = new InferenceRulesEngine();
然后,您可以按以下方式触发注册规则:
rulesEngine.fire(rules, facts);
规则引擎参数
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();
这允许您在创建引擎后重置引擎参数。
开始测试案例
1.创建项目(通过maven骨架创建)
mvn archetype:generate \
-DarchetypeGroupId=org.jeasy \
-DarchetypeArtifactId=easy-rules-archetype \
-DarchetypeVersion=4.0.0
创建完成后,会默认生成一个HelloWorldRule规则
入过启动报错 Error:(26, 34) java: 程序包org.jeasy.rules.annotation不存在
File -->settings —>maven —>Runner ,勾选:Dele… 然后保存
重写启动
依赖:
测试案例一:
1.用到的实体
package org.testRule.one;
/**
* @author: YXY
* @date: 2021/6/23 15:22
* @Version 1.0
*/
public class User {
private Integer age;
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
2.测试类
package org.testRule.one;
import org.jeasy.rules.annotation.Rule;
import org.jeasy.rules.api.Facts;
import org.jeasy.rules.api.Rules;
import org.jeasy.rules.api.RulesEngine;
import org.jeasy.rules.core.DefaultRulesEngine;
import org.jeasy.rules.mvel.MVELRule;
import java.util.HashMap;
import java.util.Map;
/**
* @author: YXY
* @date: 2021/6/23 15:21
* @Version 1.0
*/
public class MVELTestRule {
public static void main(String[] args) {
//规则引擎
RulesEngine rulesEngine = new DefaultRulesEngine();
//规则
MVELRule ageRule = new MVELRule()
.name("my rule")
.description("test demo rule")
.priority(1)
.when("user.age > 18")
.then("map.put('code',200);map.put('msg','success');");
Rules rules = new Rules();
rules.register(ageRule);
Facts facts = new Facts();
User user = new User();
user.setAge(19);
facts.put("user",user);
Map map = new HashMap();
facts.put("map",map);
rulesEngine.fire(rules,facts);
System.out.println(map);
}
}
3.运行结果
测试案例二:
1.定义规则
package org.testRule.tow.rules;
import org.jeasy.rules.annotation.*;
/**
* @author: YXY
* @date: 2021/6/23 15:19
* @Version 1.0
*/
@Rule(name = "被2整除")
public class TwoRule {
@Condition
public boolean isTwo(@Fact("num") int num){
System.out.println("---isTwo----run----");
return num % 2 == 0;
}
@Action
public void action(@Fact("num") int num){
System.out.println(num + " 被2整除");
}
@Priority
public int getPriority(){
return 1;
}
}
package org.testRule.tow.rules;
import org.jeasy.rules.annotation.*;
/**
* @author: YXY
* @date: 2021/6/23 15:19
* @Version 1.0
*/
@Rule(name = "被3整除")
public class ThreeRule {
@Condition //条件判断注解:如果return true, 执行Action
public boolean isThree(@Fact("num") int num){
System.out.println("---isThree----run----");
return num % 3 == 0;
}
@Action
public void action(@Fact("num") int num){
System.out.println(num + " 被3整除");
}
@Priority //优先级注解:return 数值越小,优先级越高
public int getPriority(){
return 2;
}
}
package org.testRule.tow.rules;
import org.jeasy.rules.annotation.Rule;
import org.jeasy.rules.support.composite.UnitRuleGroup;
/**
* @author: YXY
* @date: 2021/6/23 15:19
* @Version 1.0
*/
@Rule(name = "被2和3同时整除")
public class TwoThreeRuleUnitGroup extends UnitRuleGroup {
public TwoThreeRuleUnitGroup(Object... rules) {
for (Object rule : rules) {
addRule(rule);
}
}
@Override
public int getPriority() {
return 0;
}
}
package org.testRule.tow.rules;
import org.jeasy.rules.annotation.*;
/**
* @author: YXY
* @date: 2021/6/23 15:20
* @Version 1.0
*/
@Rule(name = "既不被2整除也不被3整除")
public class OtherRule {
@Condition
public boolean isOther(@Fact("num") int num){
System.out.println("---isOther----run----");
return num % 2 != 0 && num % 3 != 0;
}
@Action
public void action(@Fact("num") int num){
System.out.print(num);
}
@Priority
public int getPriority(){
return 3;
}
}
2.测试类
package org.testRule.tow;
import org.jeasy.rules.api.Facts;
import org.jeasy.rules.api.Rules;
import org.jeasy.rules.api.RulesEngine;
import org.jeasy.rules.core.DefaultRulesEngine;
import org.testRule.tow.rules.OtherRule;
import org.testRule.tow.rules.ThreeRule;
import org.testRule.tow.rules.TwoRule;
import org.testRule.tow.rules.TwoThreeRuleUnitGroup;
/**
* @author: YXY
* @date: 2021/6/23 15:21
* @Version 1.0
*/
public class RuleTest {
public static void main(String[] args) {
RulesEngine rulesEngine = new DefaultRulesEngine();
//创建规则
Rules rules = new Rules();
rules.register(new TwoRule());
rules.register(new ThreeRule());
rules.register(new TwoThreeRuleUnitGroup(new TwoRule(), new ThreeRule()));
rules.register(new OtherRule());
//设置真实数据
Facts facts = new Facts();
for (int i=1 ; i<=10 ; i++){
//规则因素,对应的name,要和规则里面的@Fact 一致
facts.put("num", i);
//执行规则
rulesEngine.fire(rules, facts);
System.out.println();
}
}
}
3.运行结果
控制台乱码解决:
-Dfile.encoding=UTF-8