规则引擎简介
在企业应用中,经常需要根据一组业务规则进行操作和决策。硬编码这些规则直接到应用程序中,不仅会使得代码难以管理,还会导致业务逻辑与应用逻辑的混合,降低了系统的灵活性和可维护性。规则引擎的出现,正是为了解决这些问题。
1.1 什么是规则引擎?
规则引擎是一种软件系统,它使用定义好的业务规则来评估和执行相应的行为。它通常用于实现动态决策过程,使得规则可以在不更改应用程序代码的情况下进行修改和扩展。
1.2 规则引擎的优势
解耦业务逻辑:将业务规则从程序代码中分离,提高了代码的清晰度和可维护性。
灵活性增强:业务规则更改时,无需重新编译代码,提供了更高的业务灵活性。
业务逻辑集中管理:所有的业务规则集中存储和管理,便于审查、版本控制和一致性检查。
复杂决策处理:规则引擎可以处理复杂的决策逻辑,支持多个条件和动作的组合,使得复杂业务逻辑的实现更加简洁和直观。
1.3 常见的Java规则引擎区别
以下是基于使用人数、社区活跃度和使用规模排序,整理出的常见的Java规则引擎,Drools、Easy Rules、Esper、OpenRules和JRuleEngine五个Java规则引擎的区别
排名 | 规则引擎 | 特性与区别 | 优点 | 缺点 |
---|---|---|---|---|
1 | Drools | 丰富的功能集(复杂事件处理、规则流、决策表等),广泛使用,企业级解决方案 | 功能强大、广泛的社区支持、良好的文档、支持复杂规则 | 学习曲线陡峭、复杂的配置、潜在的性能开销 |
2 | Esper | 高性能复杂事件处理引擎,适用于实时数据流和事件流 | 高性能、适合实时数据处理、支持复杂事件处理 | 学习曲线较陡、针对特定场景(事件处理)的应用更为适合 |
3 | OpenRules | 商业规则管理系统,支持用户定义和管理业务规则,适用于业务用户和开发人员 | 用户友好、强大的规则管理功能、支持业务用户和开发人员的协作 | 商业软件,可能涉及费用、学习曲线取决于具体使用场景 |
4 | Easy Rules | 轻量级,易于使用和集成,适合处理简单规则 | 简洁易用、学习曲线平缓、轻量级、快速上手 | 功能有限、不适合处理复杂规则、社区支持相对较少 |
5 | JRuleEngine | 基于Java的开源规则引擎,支持基于XML定义的规则 | 开源免费、简单易用、基于XML定义规则 | 社区和文档支持相对较少、功能相对简单、不适合处理非常复杂的规则 |
规则引擎有很多,大家根据根据自己的需求,项目需求,开发人员技术栈来选择合适规则引擎时开发。
下面的话,我们主要以Easy Rules规则引擎展开讲述。
Java Easy Rules规则引擎
2.1 概述
Easy Rules 是一个用Java编写的简单而强大的规则引擎,旨在简化规则引擎的使用。它的设计目标是使得开发者能够轻松上手,并能够快速实施业务逻辑。
2. 2 主要特性
- 轻量级:Easy Rules 是轻量级的,没有复杂的依赖,可以很方便地集成到任何Java应用中。
- 易于使用:提供简洁的API,业务规则可以以简单的Java对象或MVEL表达式形式定义。
- 灵活性:支持复合规则、条件规则和动作规则,可以处理复杂的业务逻辑。
- 可扩展性:通过添加新的规则来扩展系统功能。
2.3 如何使用 Easy Rules
2.3.1 引入依赖
在你的 pom.xml
文件中添加以下依赖:
<!-- Easy Rules 核心库依赖 -->
<dependency>
<groupId>org.jeasy</groupId>
<artifactId>easy-rules-core</artifactId>
<version>4.1.0</version>
</dependency>
<!-- Easy Rules MVEL 扩展库依赖 -->
<dependency>
<groupId>org.jeasy</groupId>
<artifactId>easy-rules-mvel</artifactId>
<version>4.1.0</version>
</dependency>
<!-- 解析文件,如json或者yml或各种其他工具类支持-->
<dependency>
<groupId>org.jeasy</groupId>
<artifactId>easy-rules-support</artifactId>
<version>4.1.0</version>
</dependency>
easy-rules-core
是 Easy Rules 的核心库,包括了规则引擎的主要功能和基础设施。它提供了定义、管理和执行规则的基本API和实现。
- 规则定义:允许你使用Java类或注解来定义规则。
- 规则管理:提供了规则注册和组织的功能。
- 规则执行:包含用于执行规则引擎的核心逻辑。
- 核心API:包含如 Rule, Rules, RulesEngine, Facts 等核心接口和类。
easy-rules-mvel
扩展了 Easy Rules,允许你使用 MVEL 表达式语言来定义规则。这使得规则可以以更加动态和灵活的方式定义,而不需要编写Java代码。
- MVEL 表达式:能够使用简单而强大的MVEL表达式来定义规则的条件和动作。
- 动态规则:可以在运行时加载和解析规则,不需要重新编译代码。
- 灵活性:适合那些需要高灵活性和动态配置的场景,特别是在规则定义需要频繁变化的系统中。
easy-rules-support
模块提供了一系列帮助类和功能,这些功能旨在简化和增强基于 Easy Rules 框架的规则引擎的开发。以下是该模块的一些主要功能:
- SpEL: 提供对 Spring Expression Language (SpEL) 的支持,使你可以使用 SpEL 来编写规则条件和执行操作。
- MVEL: 提供对 MVEL (MVFLEX Expression Language) 的支持,使你能够使用 MVEL 语言编写规则条件和执行操作。
- JMX 规则监控:支持通过 JMX (Java Management Extensions) 监控规则引擎,使你能够实时监控规则的执行情况。
- 规则引擎配置文件支持:允许从外部配置文件(例如 YAML 或 JSON 文件)加载规则,从而使规则定义更加灵活和易于管理。
- 其他辅助功能:提供了各种辅助类和实用工具,以简化规则的定义、管理和执行。
2.3.2 引子
假如我们有这么一个需求,
- 情况A:如果能被2整除的数,输出:我能被2整除;
- 情况B:如果能被3整除的数,输出:我能被3整除;
- 情况C:如果能被2整除的数且能被3整除的数,输出:我既可被2整除,也能被3整除;
只能输出一种情况,且情况C的优先级最高
。用java代码中的if–else-if实现的话
@Test
public void test1(){
int number = 12;
if (number % 2 == 0 && number % 3 == 0) {
//情况C
System.out.println("我既可被2整除,也能被3整除");
} else if (number % 2 == 0) {
//情况A
System.out.println("我能被2整除");
} else if (number % 3 == 0) {
//情况B
System.out.println("我能被3整除");
} else {
System.out.println("我既不能被2整除,也不能被3整除");
}
}
了解规则引擎,我们先了解几个概念,
facts:表示当前被传入的key:value结构的参数.它们可以是任何类型的对象,通常是领域模型对象
rule:代表一组特定条件和操作,它由条件(Conditions)和动作(Actions)组成。可以通过继承BasicRule类或使用@Rule注解来定义规则
Condition:是规则的前提条件,决定规则是否应该被触发。可以通过实现Condition接口或使用Lambda表达式来定义条件
Action:是当条件满足时应该执行的操作。可以通过实现Action接口或使用Lambda表达式来定义动作
那我们用EasyRules要怎么实现呢,下面通过多种方式实现。
定义规则
定义规则有多种方式,如:注解
,链式编程
,表达式
,yml配置文件
方式一:注解
@Rule注解
可以把规则理解为if语句和满足条件后的执行体,当 @Condition注解的方法返回真的时候则执行@Action注解的 方法
@Rule:写在Rule类上,标识这是一个规则,
可选参数:
- name(规则命名空间的唯一规则名称),
- description(描述规则的作用),
- priority(设置规则的优先级,值越低优先级越高,不设置默认为**-1**,可以通过这个设置默认的响应规则)
@Condition: 写在方法上作为判断条件,为真的时候返回True并执行**@Action方法,返回False**的时候则直接跳过
@Action:Condition:条件判断注解:如果return true, 执行Action
情况A: 能被2整除。
import org.jeasy.rules.annotation.Action;
import org.jeasy.rules.annotation.Condition;
import org.jeasy.rules.annotation.Fact;
import org.jeasy.rules.annotation.Rule;
@Rule(name = "被2整除", description = "number如果被2整除,打印:我能被2整除;", priority = 1)
public class RuleA {
/**
* Condition:条件判断注解:如果return true, 执行Action
*/
@Condition
public boolean isA(@Fact("number") int number) {
return number % 2 == 0;
}
// Action 执行方法注解
@Action
public void aAction(@Fact("number")int number) {
System.out.println("打印:"+number+"能被2整除;");
}
}
情况B: 能被3整除。
import org.jeasy.rules.annotation.Action;
import org.jeasy.rules.annotation.Condition;
import org.jeasy.rules.annotation.Fact;
import org.jeasy.rules.annotation.Rule;
@Rule(name = "被3整除", description = "number如果被3整除,打印:我能被3整除;", priority = 1)
public class RuleB {
/**
* Condition:条件判断注解:如果return true, 执行Action
*/
@Condition
public boolean isB(@Fact("number")int number) {
return number % 3 == 0;
}
// Action 执行方法注解
@Action
public void bAction(@Fact("number")int number) {
System.out.println("打印:"+number+"能被3整除;");
}
}
情况C: 同时满足A,B情况,既能被2整除也能被3整除
import org.jeasy.rules.annotation.Rule;
import org.jeasy.rules.support.composite.UnitRuleGroup;
//这里设置的priority = 0,优先级为0,但是不生效,所以直接重写getPriority方法
//@Rule(name = "被2和3整除", description = "number如果被2和3整除,打印:我能被2和3整除;", priority = 0)
public class RuleC extends UnitRuleGroup {
// 通过传入多参数的构造方法,传入其他规则进行组合
public RuleC(Object... rules) {
for (Object rule : rules) {
addRule(rule);
}
}
//也可以不通过继承UnitRuleGroup,而像A,B规则一样,用@Condition和@Action注解
//@Condition中规则写成number % 2 == 0 && number % 3 == 0
//重写priority,优先级设置方法。默认为1,先设置为0 ,越小优先级越高
@Override
public int getPriority() {
return 0;
}
}
测试方法:
import org.jeasy.rules.api.Facts;
import org.jeasy.rules.api.Rules;
import org.jeasy.rules.api.RulesEngineParameters;
import org.jeasy.rules.core.DefaultRulesEngine;
import org.junit.Test;
public class Test2 {
public static void main(String[] args) {
// 初始化规则引擎参数,设置为当一个规则成功应用时跳过余下的规则
RulesEngineParameters parameters = new RulesEngineParameters()
//当一个规则成功满足应用时,跳过余下的规则。不设置默认是都执行,类似一个一个的if
.skipOnFirstAppliedRule(true);
// 使用指定的参数创建默认规则引擎
DefaultRulesEngine engine = new DefaultRulesEngine(parameters);
// 创建规则集
Rules rules = new Rules();
// 注册独立规则 RuleA
rules.register(new RuleA());
// 注册独立规则 RuleB
rules.register(new RuleB());
// 注册组合规则 RuleC,其中包含 RuleA 和 RuleB
//通过继承UnitRuleGroup方法,将A,B的规则传进来
rules.register(new RuleC(new RuleA(), new RuleB()));
// 创建事实集,并添加一个名为 "number" 的事实,值为 12
Facts facts = new Facts();
facts.put("number", 12);
// 触发规则引擎,执行所有符合条件的规则
engine.fire(rules, facts);
}
}
最终输出:
说明还是先执行了情况C的,满足我们之前的需求逻辑的。
方式二:链式编程
Rule rule = new RuleBuilder();
import org.jeasy.rules.api.*;
import org.jeasy.rules.core.DefaultRulesEngine;
import org.jeasy.rules.core.RuleBuilder;
public class Test2 {
public static void main(String[] args) {
// 定义规则引擎参数,设置跳过优先级较低的规则
RulesEngineParameters parameters = new RulesEngineParameters().skipOnFirstAppliedRule(true);
RulesEngine rulesEngine = new DefaultRulesEngine(parameters);
// 定义规则C:优先级最高,当数字能被2和3整除时触发
Rule ruleC = new RuleBuilder()
.name("Rule C")
.priority(1) // 设置优先级为1
.when(facts -> {
int number = facts.get("number");
return number % 2 == 0 && number % 3 == 0; // 检查是否能被2和3整除
})
.then(facts -> System.out.println("我既可被2整除,也能被3整除")) // 触发时的动作
.build();
// 定义规则A:优先级第二,当数字能被2整除时触发
Rule ruleA = new RuleBuilder()
.name("Rule A")
.priority(2) // 设置优先级为2
.when(facts -> {
int number = facts.get("number");
return number % 2 == 0; // 检查是否能被2整除
})
.then(facts -> System.out.println("我能被2整除")) // 触发时的动作
.build();
// 定义规则B:优先级最低,当数字能被3整除时触发
Rule ruleB = new RuleBuilder()
.name("Rule B")
.priority(3) // 设置优先级为3
.when(facts -> {
int number = facts.get("number");
return number % 3 == 0; // 检查是否能被3整除
})
.then(facts -> System.out.println("我能被3整除")) // 触发时的动作
.build();
// 创建规则集并添加规则
Rules rules = new Rules();
rules.register(ruleC);
rules.register(ruleA);
rules.register(ruleB);
// 定义一个Fact,包含要检查的数字
Facts facts = new Facts();
facts.put("number", 6); // 你可以更改这个数字来测试不同的情况
// 执行规则
rulesEngine.fire(rules, facts);
}
}
最终输出:
也是符合的。
方式三:表达式
//它会自己解析表达式
Rule rule = new MVELRule();
package org.example.demoword.demo3.demo3;
import org.jeasy.rules.api.Facts;
import org.jeasy.rules.api.Rules;
import org.jeasy.rules.api.RulesEngine;
import org.jeasy.rules.api.RulesEngineParameters;
import org.jeasy.rules.core.DefaultRulesEngine;
import org.jeasy.rules.mvel.MVELRule;
public class Test4 {
public static void main(String[] args) {
// 定义规则引擎参数,设置跳过优先级较低的规则
RulesEngineParameters parameters = new RulesEngineParameters().skipOnFirstAppliedRule(true);
RulesEngine rulesEngine = new DefaultRulesEngine(parameters);
// 定义规则C:优先级最高,当数字能被2和3整除时触发
MVELRule ruleC = new MVELRule()
.name("Rule C")
.priority(1) // 设置优先级为1
.when("number % 2 == 0 && number % 3 == 0") // 检查是否能被2和3整除
.then("System.out.println(\"我既可被2整除,也能被3整除\");"); // 触发时的动作
// 定义规则A:优先级第二,当数字能被2整除时触发
MVELRule ruleA = new MVELRule()
.name("Rule A")
.priority(2) // 设置优先级为2
.when("number % 2 == 0") // 检查是否能被2整除
.then("System.out.println(\"我能被2整除\");"); // 触发时的动作
// 定义规则B:优先级最低,当数字能被3整除时触发
MVELRule ruleB = new MVELRule()
.name("Rule B")
.priority(3) // 设置优先级为3
.when("number % 3 == 0") // 检查是否能被3整除
.then("System.out.println(\"我能被3整除\");"); // 触发时的动作
// 创建规则集并添加规则
Rules rules = new Rules();
rules.register(ruleC);
rules.register(ruleA);
rules.register(ruleB);
// 定义一个Fact,包含要检查的数字
Facts facts = new Facts();
facts.put("number", 6); // 你可以更改这个数字来测试不同的情况
// 执行规则
rulesEngine.fire(rules, facts);
}
}
最终输出:
也是符合的。和链式编程有点像,但是链式编程里面是调用的java的方法或者运算符,但是表达式能直接解析你写的的表达式。
方式四:使用JSON或YML文件定义规则
MVELRuleFactory ruleFactory = new MVELRuleFactory();
1. 使用JSON文件定义规则
创建一个JSON文件rules.json
,定义规则:
[
{
"name": "Rule C",
"description": "When number is divisible by 2 and 3",
"priority": 1,
"condition": "number % 2 == 0 && number % 3 == 0",
"actions": [
"System.out.println(\"我既可被2整除,也能被3整除\");"
]
},
{
"name": "Rule A",
"description": "When number is divisible by 2",
"priority": 2,
"condition": "number % 2 == 0",
"actions": [
"System.out.println(\"我能被2整除\");"
]
},
{
"name": "Rule B",
"description": "When number is divisible by 3",
"priority": 3,
"condition": "number % 3 == 0",
"actions": [
"System.out.println(\"我能被3整除\");"
]
}
]
然后,在Java代码中加载并执行这些规则:
import org.jeasy.rules.api.Facts;
import org.jeasy.rules.api.Rules;
import org.jeasy.rules.api.RulesEngine;
import org.jeasy.rules.api.RulesEngineParameters;
import org.jeasy.rules.core.DefaultRulesEngine;
import org.jeasy.rules.mvel.MVELRuleFactory;
import org.jeasy.rules.support.reader.JsonRuleDefinitionReader;
import java.io.FileReader;
public class Test5 {
public static void main(String[] args) throws Exception {
// 定义规则引擎参数,设置跳过优先级较低的规则
RulesEngineParameters parameters = new RulesEngineParameters().skipOnFirstAppliedRule(true);
RulesEngine rulesEngine = new DefaultRulesEngine(parameters);
// 从JSON文件中加载规则
MVELRuleFactory ruleFactory = new MVELRuleFactory(new JsonRuleDefinitionReader());
Rules rules = ruleFactory.createRules(new FileReader("/Users/luojiwen/Documents/classify/workspace/project/person/LJW/demo-word/demo-word/src/main/java/org/example/demoword/demo3/demo3/rules.json"));
// 定义一个Fact,包含要检查的数字
Facts facts = new Facts();
facts.put("number", 6); // 你可以更改这个数字来测试不同的情况
// 执行规则
rulesEngine.fire(rules, facts);
}
}
最终输出:
2. 使用YML文件定义规则
创建一个YML文件rules.yml
,定义规则:
name: "Rule C"
description: "When number is divisible by 2 and 3"
priority: 1
condition: "number % 2 == 0 && number % 3 == 0"
actions:
- "System.out.println(\"我既可被2整除,也能被3整除\");"
---
name: "Rule A"
description: "When number is divisible by 2"
priority: 2
condition: "number % 2 == 0"
actions:
- "System.out.println(\"我能被2整除\");"
---
name: "Rule B"
description: "When number is divisible by 3"
priority: 3
condition: "number % 3 == 0"
actions:
- "System.out.println(\"我能被3整除\");"
这里提一嘴,必须的按照这种yml格式来,才能读取到,加上---
具体缘由可以去看看,easyRules的源码和git上面的版本更新:讲了为啥要加(因为大佬说要加)
提供传送阵地址:https://github.com/j-easy/easy-rules/issues/138
但是我就是不想加,怎么办,没事,小问题
然后,在Java代码中加载并执行这些规则:
import org.jeasy.rules.api.Facts;
import org.jeasy.rules.api.Rules;
import org.jeasy.rules.api.RulesEngine;
import org.jeasy.rules.api.RulesEngineParameters;
import org.jeasy.rules.core.DefaultRulesEngine;
import org.jeasy.rules.mvel.MVELRuleFactory;
import org.jeasy.rules.support.reader.YamlRuleDefinitionReader;
import java.io.FileReader;
public class Test6 {
public static void main(String[] args) throws Exception {
// 定义规则引擎参数,设置跳过优先级较低的规则
RulesEngineParameters parameters = new RulesEngineParameters().skipOnFirstAppliedRule(true);
RulesEngine rulesEngine = new DefaultRulesEngine(parameters);
// 从YML文件中加载规则
MVELRuleFactory ruleFactory = new MVELRuleFactory(new YamlRuleDefinitionReader());
Rules rules = ruleFactory.createRules(new FileReader("/Users/luojiwen/Documents/classify/workspace/project/person/LJW/demo-word/demo-word/src/main/java/org/example/demoword/demo3/demo3/rules.yml"));
// 定义一个Fact,包含要检查的数字
Facts facts = new Facts();
facts.put("number", 6); // 你可以更改这个数字来测试不同的情况
// 执行规则
rulesEngine.fire(rules, facts);
}
}
有些小伙伴说,这加了---
,就不太是yml格式了吧,我总不能自己一个一个在自己的yml文件中加---
吧 ,
当然也是可以的,那我们换一种方式,当然方式有很多种,如自己写一个yml的解析器等等。
我这边看了他的源码,
他的源码是这样的
我们可以继承这个他的这个接口类AbstractRuleDefinitionReader且重写他的loadRules方法,就可以了
import org.jeasy.rules.core.DefaultRulesEngine;
import org.jeasy.rules.mvel.MVELRuleFactory;
import org.jeasy.rules.support.reader.AbstractRuleDefinitionReader;
import org.junit.Test;
import org.jeasy.rules.api.*;
import org.yaml.snakeyaml.Yaml;
import java.io.FileReader;
import java.io.Reader;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class TryTry7 extends AbstractRuleDefinitionReader {
private static final Yaml YAML_PARSER = new Yaml();
@Test
public void test1() throws Exception {
// 定义规则引擎参数,设置跳过优先级较低的规则
RulesEngineParameters parameters = new RulesEngineParameters().skipOnFirstAppliedRule(true);
RulesEngine rulesEngine = new DefaultRulesEngine(parameters);
MVELRuleFactory ruleFactory = new MVELRuleFactory(this);
Rules rules = ruleFactory.createRules(new FileReader("/Users/luojiwen/Documents/classify/workspace/project/person/LJW/demo-word/demo-word/src/main/java/org/example/demoword/demo3/demo3/rules.yml"));
// 定义一个Fact,包含要检查的数字
Facts facts = new Facts();
facts.put("number", 6); // 你可以更改这个数字来测试不同的情况
// 执行规则
rulesEngine.fire(rules, facts);
}
@Override
protected Iterable<Map<String, Object>> loadRules(Reader reader) throws Exception {
List<Map<String, Object>> rulesList = new ArrayList<>();
Iterable<Object> rules = YAML_PARSER.loadAll(reader);
for (Object rule : rules) {
if (rule instanceof Map) {
rulesList.add((Map<String, Object>) rule);
}else if (rule instanceof List) {
for (Object o : (List) rule) {
if (o instanceof Map) {
rulesList.add((Map<String, Object>) o);
}
}
}
}
return rulesList;
}
}
- name: Rule C
description: When number is divisible by 2 and 3
priority: 1
condition: "number % 2 == 0 && number % 3 == 0"
actions:
- "System.out.println(\"我既可被2整除,也能被3整除\");"
- name: Rule A
description: When number is divisible by 2
priority: 2
condition: "number % 2 == 0"
actions:
- "System.out.println(\"我能被2整除\");"
- name: Rule B
description: When number is divisible by 3
priority: 3
condition: "number % 3 == 0"
actions:
- "System.out.println(\"我能被3整除\");"
最终都还是输出:
也是可以达到效果的。
这里再讲一下规则引擎的一些参数。
RulesEngine
进行规则和事实进行判断的启动入口
1. RulesEngine类型:
DefaultRulesEngine
:根据规则的自然顺序(默认为优先级)应用规则。InferenceRulesEngine
:在已知的事实上不断地应用规则,直到没有更多的规则可用
2. 规则引擎的参数
参数 | 类型 | 默认值 | 描述 |
---|---|---|---|
skipOnFirstAppliedRule | boolean | false | 第一个规则应用后是否跳过 |
skipOnFirstFailedRule | boolean | false | 第一个规则失败后是否跳过 |
skipOnFirstNonTriggeredRule | boolean | false | 第一个规则未触发后是否跳过 |
rulePriorityThreshold | int | false | 规则优先级阈值 (重复项) |
rulePriorityThreshold | int | MaxInt | 规则优先级阈值 |
skipOnFirstFailedRule
skipOnFirstNonTriggeredRule
rulePriorityThreshold
skipOnFirstAppliedRule
当skipOnFirstAppliedRule
的值为true
时,类似java中的if-elseif
,只要前面的有一个规则满足,就直接跳过,不走下面的elseif
逻辑了
if(){
//....
}else if(){
//....
}else if(){
//....
}
默认情况下,都是if
if(){
//....
}
if(){
//....
}
if(){
//....
}