浅析Easy Rules规则引擎

简介: Easy Rules是一个简单而强大的Java规则引擎,提供以下功能:轻量级框架和易于学习的API基于POJO的开发与注解的编程模型定义抽象的业务规则并轻松应用它们支持从简单规则创建组合规则的能力支持使用表达式语言(如MVEL和SpEL)定义规则的能力

什么是Easy Rules?


Easy Rules是一个简单而强大的Java规则引擎,提供以下功能:

  • 轻量级框架和易于学习的API
  • 基于POJO的开发与注解的编程模型
  • 定义抽象的业务规则并轻松应用它们
  • 支持从简单规则创建组合规则的能力
  • 支持使用表达式语言(如MVEL和SpEL)定义规则的能力

在一篇非常有趣的规则引擎的文章中,Martin Fowler说:

您可以自己构建一个简单的规则引擎。您只需要创建一组具有条件和操作的对象,将它们存储在一个集合中,并运行它们来评估conditions和执行actions。

这正是Easy Rules所做的,它提供了抽象Rule来创建带有conditions和actions的规则,RulesEngine API运行一系列规则来评估conditions和执行actions。


运行环境


Easy Rules是一个Java库, 需要运行在Java 1.7及以上。

maven依赖

<!--easy rules核心库-->
<dependency>
    <groupId>org.jeasy</groupId>
    <artifactId>easy-rules-core</artifactId>
    <version>3.3.0</version>
</dependency>
<!--规则定义文件格式,支持json,yaml等-->
<dependency>
    <groupId>org.jeasy</groupId>
    <artifactId>easy-rules-support</artifactId>
    <version>3.3.0</version>
</dependency>
<!--支持mvel规则语法库-->
<dependency>
    <groupId>org.jeasy</groupId>
    <artifactId>easy-rules-mvel</artifactId>
    <version>3.3.0</version>
</dependency>
复制代码

定义规则


大多数业务规则可以由以下定义表示:

  • 名称:规则命名空间中的唯一规则名称
  • 说明:规则的简要说明
  • 优先级:相对于其他规则的规则优先级
  • 事实:去匹配规则时的一组已知事实
  • 条件:为了匹配该规则,在给定某些事实的情况下应满足的一组条件
  • 动作:当条件满足时要执行的一组动作(可以添加/删除/修改事实) Easy Rules为定义业务规则的每个关键点提供了抽象。

在Easy Rules中,一个规则由Rule接口表示:

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接口表示。

规则可以用两种不同的方式定义

  • 通过在POJO上添加注释,以声明方式定义
  • 通过RuleBuilder API,以编程方式定义


1. 用注解定义规则

这些是定义规则的最常用方法,但如果需要,还可以实现Rulei接口或继承BasicRule类。

@Rule(name = "my rule", description = "my rule description", priority = 1)
public class MyRule {
    @Condition
    public boolean when(@Fact("fact") fact) {
        //my rule conditions
        return true;
    }
    @Action(order = 1)
    public void then(Facts facts) throws Exception {
        //my actions
    }
    @Action(order = 2)
    public void finally() throws Exception {
        //my final actions
    }
}
复制代码

@Condition注解标记计算规则条件的方法。此方法必须是公共的,可以有一个或多个用@Fact注解的参数,并返回布尔类型。只有一个方法能用@Condition注解。

@Action注解标记要执行规则操作的方法。规则可以有多个操作。可以使用order属性按指定的顺序执行操作。默认情况下,操作的顺序为0。


2. 用RuleBuilder API定义规则

Rule rule = new RuleBuilder()
                .name("myRule")
                .description("myRuleDescription")
                .priority(3)
                .when(condition)
                .then(action1)
                .then(action2)
                .build();
复制代码

在这个例子中, Condition实例condition,Action实例是action1和action2。


定义事实


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异常.


定义规则引擎


从版本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();
复制代码

这允许您在创建引擎后重置引擎参数。


hello world 例子



我们将创建一个始终被触发的规则,在执行时将“hello world”打印到控制台。规则如下:

@Rule(name = "Hello World rule", description = "Always say hello world")
public class HelloWorldRule {
    @Condition
    public boolean when() {
        return true;
    }
    @Action
    public void then() throws Exception {
        System.out.println("hello world");
    }
}
复制代码

现在,让我们创建一个规则引擎并触发此规则

public class Launcher {
    public static void main(String[] args) {
        // create facts
        Facts facts = new Facts();
        // create rules
        Rules rules = new Rules();
        rules.register(new HelloWorldRule());
        // create a rules engine and fire rules on known facts
        RulesEngine rulesEngine = new DefaultRulesEngine();
        rulesEngine.fire(rules, facts);
    }
}
复制代码

输出结果:

INFO: Engine parameters { skipOnFirstAppliedRule = false, skipOnFirstNonTriggeredRule = false, skipOnFirstFailedRule = false, priorityThreshold = 2147483647 }
INFO: Registered rules:
INFO: Rule { name = 'Hello World rule', description = 'Always say hello world', priority = '2147483646'}
INFO: Rules evaluation started
INFO: Rule 'Hello World rule' triggered
Hello world
INFO: Rule 'Hello World rule' performed successfully
复制代码

ok, 大功告成了。


相关文章
|
Java
RestfulTool
RestfulTool
60 0
|
存储 Go 开发工具
GOPATH 模式怎么迁移至 Modules 模式?
GOPATH 模式怎么迁移至 Modules 模式?
99 0
|
存储 缓存 Prometheus
听GPT 讲Prometheus源代码--rules
听GPT 讲Prometheus源代码--rules
73 0
|
Python
Python 库配置问题:“Couldn‘t find ffmpeg or avconv - defaulting to ffmpeg, but may not work“. 解决办法
Python 库配置问题:“Couldn‘t find ffmpeg or avconv - defaulting to ffmpeg, but may not work“. 解决办法
948 0
Python 库配置问题:“Couldn‘t find ffmpeg or avconv - defaulting to ffmpeg, but may not work“. 解决办法
|
Java
easy-rules规则引擎最佳落地实践
写作目的 这是一个头部互联网公司中的一个问题。因为有很多业务产品线,作为一个新人或者团队外的人员是很难区分不同的产品线之间的区别的,因此需要给某个产品线一个描述。但是随着业务的发展,产品线下可能又根据某个字段进一步划分,那么子产品线就是父产品线 + 字段 去区分。后面根据两个字段划分…。人都麻了。因为不同的组合有不同的链路。因此针对一个产品,我们要提供针对这个产品的具体规则描述,从而减少答疑。
949 0
easy-rules规则引擎最佳落地实践
|
前端开发 JavaScript 计算机视觉
Module Federation最佳实践
Module Federation[1]官方称为模块联邦,模块联邦是webpack5支持的一个最新特性,多个独立构建的应用,可以组成一个应用,这些独立的应用不存在依赖关系,可以独立部署,官方称为微前端。
630 0
Module Federation最佳实践
|
JSON 前端开发 JavaScript
Cypress系列(72)- 详解 Module API
Cypress系列(72)- 详解 Module API
399 0
Cypress系列(72)- 详解 Module API
|
资源调度 JavaScript
ts搭建组件库
想要搭建组件库,必定要有好的模仿样例,比如element-ui。阅读源码就是最好的学习路径。 本文主要内容:从零搭建自己的组件库,并进行组件测试。 涉及技术:vue + typeScript + scss + chai + mocha + karma。
385 0
Libra教程之:运行自定义move modules
Libra教程之:运行自定义move modules
|
API Python Windows
成功解决Scrapy框架的问题ModuleNotFoundError: No module named 'win32api'
成功解决Scrapy框架的问题ModuleNotFoundError: No module named 'win32api'
成功解决Scrapy框架的问题ModuleNotFoundError: No module named 'win32api'