规则引擎 Aviator&Drools&&Mvel&EasyRules

零、RuleEngine

github:Hale-Lee/RuleEngine

一、Aviator

1、maven

<dependency>
    <groupId>com.googlecode.aviator</groupId>
    <artifactId>aviator</artifactId>
    <version>${aviator.version}</version>
</dependency>

2、特性

  • (1) 支持大部分运算操作符:包括算术操作符、关系运算符、逻辑操作符、正则匹配操作符 =~、三元表达式 ?:

    注:支持操作符的优先级和括号强制优先级

  • (2) 支持函数调用和自定义函数
  • (3) 支持正则表达式匹配,类 Ruby 的 $digit 指向匹配分组、自动类型转换

    注:当执行操作时,会自动判断操作数类型并做相应转换,无法转换即抛异常

  • (4) 支持传入变量,支持类似 a.b.c 的嵌套变量访问
  • (5) 性能优秀
  • (6) 劣势:没有 if elsedo while 等语句;没有赋值语句;仅支持逻辑表达式、算术表达式、三元表达式和正则匹配;没有位运算符

3、执行方式

两种表达式执行方式:

  • execute():需要传递 Map 格式参数
  • exec():不需要传递 Map
public class Test {
    public static void main(String[] args) {
        // exec执行方式,无需传递Map格式
        String age = "18";
        System.out.println(AviatorEvaluator.exec("'His age is '+ age +'!'", age));

        // execute执行方式,需传递Map格式
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("age", "18");
        System.out.println(AviatorEvaluator.execute("'His age is '+ age +'!'", map));
    }
}

AviatorEvaluator 两种模式:

  • 以编译速度优先(默认):AviatorEvaluator.setOptimize(AviatorEvaluator.COMPILE)
  • 以运行速度优先:AviatorEvaluator.setOptimize(AviatorEvaluator.EVAL)

AviatorEvaluator.execute 内存溢出 OOM

  • 背景:循环调用 AviatorEvaluator.execute 时,JVM 非堆内存会慢慢增长,随着时间的推移,那段循环的代码被执行次数越多,非堆内存最终会因为只有增加没有释放而爆了
  • 解决:如果表达式规模有限,都建议启用 cache 模式,除了 class 内存占用之外还可以提升性能
    public static Object execute(String expression, Map<String, Object> env, boolean cached) //cached = true
    

详情链接:循环调用AviatorEvaluator.execute的时候会出现问题

4、内置函数与自定义函数

(1) 内置函数

  • 详情可参考:https://juejin.cn/post/7002606430519853069
public class Test {
    public static void main(String[] args) {
        Map<String,Object> map = new HashMap<>();
        map.put("s1","123qwer");
        map.put("s2","123");
  		System.out.println(AviatorEvaluator.execute("string.startsWith(s1,s2)",map));
    }
}

(2) 自定义函数

自定义函数要继承AbstractFunction类,重写目标方法
可阅读:https://ixyzero.com/blog/archives/3884.html

public class Test {
    public static void main(String[] args) {
        AviatorEvaluator.addFunction(new MultiplyFunction()); // 注册自定义函数
        // 方式1
        System.out.println(AviatorEvaluator.execute("multiply(12.23, -2.3)")); 
        // 方式2
        Map<String, Object> params = new HashMap<>(); 
        params.put("a", 12.23);
        params.put("b", -2.3);
        System.out.println(AviatorEvaluator.execute("multiply(a, b)", params));
    }
}

class MultiplyFunction extends AbstractFunction{
    @Override
    public AviatorObject call(Map<String, Object> env, AviatorObject arg1, AviatorObject arg2) {
        double num1 = FunctionUtils.getNumberValue(arg1, env).doubleValue();
        double num2 = FunctionUtils.getNumberValue(arg2, env).doubleValue();
        return new AviatorDouble(num1 * num2);
    }

    @Override
    public String getName() {
        return "multiply";
    }
}

5、数据类型

  • Number 类型:数字类型,支持两种类型,分别对应 Java 的 Long 和 Double
    • 整数被转换为 Long,浮点数被转换为 Double
    • 不支持科学计数法,仅支持十进制
  • String 类型: 字符串类型,单引号或双引号括起来的文本串,如’hello world’

    变量传入的 String 或 Character 将被转为 String 类型

  • Bool 类型: 常量 true 和 false,表示真值和假值,与 java 的 Boolean.TRUEBoolean.False 对应
  • Pattern 类型: 正则表达式,以 // 括起来的字符串,如 //d+/,内部实现为 java.util.Pattern
  • 变量类型: 与 Java 的变量命名规则相同,变量的值由用户传入
  • nil 类型: 类似 java 的 null,但 nil 可以参与 ==、!=、>、>=、<、<= 的比较

    Aviator 规定:

    • 除了 nil 本身,任何类型都大于 nil
    • nil == nil 返回 true
    • 用户传入的变量值若为 null,则将作为 nil 处理,nil 打印为 null

6、常用操作符

(1) 操作符列表

在这里插入图片描述

(2) 常量和变量

在这里插入图片描述

(3) 算术运算符

算术运算符:+、-、<tt></tt>、/、%

其中:

  • -、<tt></tt>、/、% 仅能作用于Number类型
  • + 能用于 Number 类型和 String 类型
    注:任何类型与String 相加,结果为 String

(4) 逻辑运算符

逻辑运算符包括:一元否定运算符 !、逻辑与 &&、逻辑或 ||

注:逻辑运算符的操作数只能为 Boolean

(5) 关系运算符

关系运算符包括:<、<=、>、>=、==、!=

注:

  • &&|| 都执行短路规则
  • 可以用作 Number之间、String之间、Pattern之间、Boolean之间、变量之间、其他类型与 nil 之间的关系比较

(6) 匹配运算符

匹配运算符 =~ 用于 String 和 Pattern 的匹配

注:

  • 左操作数必须为 String,右操作数必须为 Pattern
  • 匹配成功后,Pattern 的分组将存于变量 $num,num 为分组索引

(7) 三元运算符

三元运算符 ?:,形式为 bool ? exp1: exp2

其中 bool 必须为结果为 Boolean 类型的表达式,而 exp1 和 exp2 可以为任何合法的 Aviator 表达式,并且不要求 exp1 和 exp2 返回的结果类型一致

Aviator 没有提供 if else 语句

8、案例

(1) 案例一:编译表达式

public class Test {
   public static void main(String[] args) {
       String expression = "a+(b-c)>100";
       // 编译表达式
       Expression compiledExp = AviatorEvaluator.compile(expression);

       Map<String, Object> env = new HashMap<>();
       env.put("a", 100.3);
       env.put("b", 45);
       env.put("c", -199.100);

       // 执行表达式
       Boolean result = (Boolean) compiledExp.execute(env);
       System.out.println(result);
   }
}

(2) 案例二:访问数组和集合

List 和数组用 list[0]array[0]Mapmap.date

public class Test {
    public static void main(String[] args) {
        final List<String> list = new ArrayList<>();
        list.add("hello");
        list.add(" world");

        final int[] array = new int[3];
        array[0] = 0;
        array[1] = 1;
        array[2] = 3;

        final Map<String, Date> map = new HashMap<>();
        map.put("date", new Date());

        Map<String, Object> env = new HashMap<>();
        env.put("list", list);
        env.put("array", array);
        env.put("map", map);

        System.out.println(AviatorEvaluator.execute("list[0]+':'+array[0]+':'+'today is '+map.date", env));
    }
}

(3) 案例三:三元比较符

public class Test {
    public static void main(String[] args) {
        Map<String, Object> env = new HashMap<String, Object>();
        env.put("a", -5);
        String result = (String) AviatorEvaluator.execute("a>0? 'yes':'no'", env);
        System.out.println(result);
    }
}

(4) 案例四:正则表达式匹配

public class Test {
    public static void main(String[] args) {
        String email = "hello2018@gmail.com";
        Map<String, Object> env = new HashMap<String, Object>();
        env.put("email", email);
        String username = (String) AviatorEvaluator.execute("email=~/([\\w0-8]+)@\\w+[\\.\\w+]+/ ? $1 : 'unknow' ", env);
        System.out.println(username);
    }
}

(5) 案例五:变量的语法糖衣

public class Test {
    public static void main(String[] args) {
        User user = new User(1,"jack","18");
        Map<String, Object> env = new HashMap<>();
        env.put("user", user);
        String result = (String) AviatorEvaluator.execute(" '[user id='+ user.id + ',name='+user.name + ',age=' +user.age +']' ", env);
        System.out.println(result);
    }
}

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
    private int id;
    private String name;
    private String age;
}

(6) 案例六:nil 对象

  • nil 是 Aviator 的内置常量,表示空值
  • nil 与 null 的区别:null 只能使用 ==、!=,而 nil 还可以使用 >、>=、<、<=
  • 用户传入的变量若为 null,将自动以 nil 替代
  • 除了 nil 本身,任何对象都比 nil 大
AviatorEvaluator.execute("nil == nil");   //true 
AviatorEvaluator.execute(" 3 > nil");      //true 
AviatorEvaluator.execute(" true != nil");  //true 
AviatorEvaluator.execute(" ' ' > nil ");    //true 
AviatorEvaluator.execute(" a == nil ");     //true, a is null

(7) 案例七:日期比较

public class Test {
    public static void main(String[] args) {
        Map<String, Object> env = new HashMap<String, Object>();
        final Date date = new Date();
        String dateStr = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SS").format(date);
        env.put("date", date);
        env.put("dateStr", dateStr);

        Boolean result = (Boolean) AviatorEvaluator.execute("date==dateStr", env);
        System.out.println(result);

        result = (Boolean) AviatorEvaluator.execute("date > '2009-12-20 00:00:00:00' ", env);
        System.out.println(result);

        result = (Boolean) AviatorEvaluator.execute("date < '2200-12-20 00:00:00:00' ", env);
        System.out.println(result);

        result = (Boolean) AviatorEvaluator.execute("date ==date ", env);
        System.out.println(result);
    }
}

(8) 案例八:变量定义

Map<String, Object> expParams = Maps.newHashMap();
expParams.put("work", 13);
Object execute = AviatorUtils.execute("let year = (work <= 0 ? 0 : work/12); let month = work%12; let s0 = \"null\"; let s1 = \"#{month}月\"; let s2 = \"#{year}年\";let s3 = \"#{year}年#{month}月\";work <= 0 ? s0 : (work < 12 ? s1 : (month == 0)?s2:s3)", expParams, true);
System.out.println(execute.toString());

二、Drools

在这里插入图片描述

1、优点

  • (1) 简化系统架构,优化应用
  • (2) 提高系统的可维护性和维护成本
  • (3) 方便系统的整合
  • (4) 减少编写“硬代码”业务规则的成本和风险

2、使用

(1) maven

<dependencies>
    <dependency>
        <groupId>org.kie</groupId>
        <artifactId>kie-api</artifactId>
        <version>6.5.0.Final</version>
    </dependency>
    <dependency>
        <groupId>org.drools</groupId>
        <artifactId>drools-compiler</artifactId>
        <version>6.5.0.Final</version>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
    </dependency>
</dependencies>

(2) 配置文件 /src/resources/META-INF/kmodule.xml

<?xml version="1.0" encoding="UTF-8"?>
<kmodule xmlns="http://jboss.org/kie/6.0.0/kmodule">
    <kbase name="rules" packages="rules">
        <ksession name="myAgeSession"/>
    </kbase>
</kmodule>

(3) drools规则文件 /src/resources/rules/age.drl

import com.lrq.wechatDemo.domain.User               // 导入类

dialect  "mvel"

rule "age"                                      // 规则名,唯一
    when
        $user : User(age<15 || age>60)     //规则的条件部分
    then
        System.out.println("年龄不符合要求!");
end

(4) 案例

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath*:applicationContext.xml"})
public class TestUser {
    private static KieContainer container = null;
    private KieSession statefulKieSession = null;

    @Test
    public void test(){
        KieServices kieServices = KieServices.Factory.get();
        container = kieServices.getKieClasspathContainer();
        statefulKieSession = container.newKieSession("myAgeSession");
        User user = new User("duval yang",12);
        statefulKieSession.insert(user);
        statefulKieSession.fireAllRules();
        statefulKieSession.dispose();
    }
}

三、MVEL

MVEL 用于执行使用 Java 语法编写的表达式

<dependency>
	<groupId>org.mvel</groupId>
    <artifactId>mvel2</artifactId>
    <version>2.3.1.Final</version>
</dependency>

1、特性

MVEL 是一个功能强大的基于 Java 应用程序的表达式语言

特性:

  • (1) 动态 JIT 优化器:当负载超过一个确保代码产生的阈值时,选择性地产生字节代码,减少内存使用量

    新的静态类型检查和属性支持,允许集成类型安全表达

  • (2) 错误报告的改善:包括行和列的错误信息
  • (3) 新的脚本语言特征:包含函数定义,如:闭包、lambda定义、标准循环构造(for, while, do-while, do-until…)、空值安全导航操作、内联 with-context 运营 、易变的(isdef)的测试运营等等
  • (4) 改进的集成功能:支持基础类型的个性化属性处理器,集成到 JIT 中
  • (5) 更快的模板引擎:支持线性模板定义,宏定义和个性化标记定义
  • (6) 新的交互式shell MVELSH
  • (7) 缺少可选类型安全
  • (8) 集成不良:通过映射填入内容
    • 没有字节码不能运作用字节码生成编译时间慢,还增加了可扩展性问题
    • 不用字节码生成运行时执行非常慢
  • (9) 内存消耗过大
  • (10) Jar 巨大/依赖规模

与 java 不同,MVEL 是动态类型,即在源文件中没有类型限制
如:一条 MVEL 表达式,可以是单个标识符,也可以是一个充满了方法调用和内部集合创建的庞大的布尔表达式

2、案例

public class MvelUtils {
    public static void main(String[] args) {
        String expression = "a == null && b == nil ";
        Map<String,Object> map = Maps.newHashMap();
        map.put("a",null);
        map.put("b",null);

        Object object = MVEL.eval(expression,map);
        System.out.println(object);
    }
}

四、EasyRules

easy-rules 已集成了mvel 规则表达式,后续可能集成 SpEL

1、特性

easy rules 是一个简单而强大的 java 规则引擎,有以下特性:

  • 轻量级框架,学习成本低
  • 基于 POJO
  • 为定义业务引擎提供有用的抽象和简便的应用
  • 从原始的规则组合成复杂的规则

主要的类或接口Rule、RulesEngine、RuleListener、Facts
主要的注解@Action、@Condition、@Fact、@Priority、@Rule

  • @Rule:可以标注 name 和 description 属性,每个rule的name要唯一,如果没有指定,则RuleProxy则默认取类名
  • @Condition:条件判断,要求返回 boolean 值,表示是否满足条件
  • @Action:标注条件成立之后触发的方法
  • @Priority:标注该 rule 的优先级,默认 Integer.MAX_VALUE - 1,值越小越优先
  • @Fact:在客户端使用 put 方法向 Facts 中添加数据,在规则文件中通过 key 来得到相应的数据

    Facts 的用法很像 Map,是客户端和规则文件之间通信的桥梁

2、使用

(1) Java 方式

  • 先创建规则并标注属性
public class RuleClass {
    @Rule(priority = 1) //规则设定优先级
    public static class FizzRule {
        @Condition
        public boolean isFizz(@Fact("number") Integer number) {
            return number % 5 == 0;
        }

        @Action
        public void printFizz() {
            System.out.print("fizz\n");
        }
    }

    @Rule(priority = 2)
    public static class BuzzRule {
        @Condition
        public boolean isBuzz(@Fact("number") Integer number) {
            return number % 7 == 0;
        }

        @Action
        public void printBuzz() {
            System.out.print("buzz\n");
        }
    }

    public static class FizzBuzzRule extends UnitRuleGroup {
        public FizzBuzzRule(Object... rules) {
            for (Object rule : rules) {
                addRule(rule);
            }
        }

        @Override
        public int getPriority() {
            return 0;
        }
    }

    @Rule(priority = 3)
    public static class NonFizzBuzzRule {
        @Condition
        public boolean isNotFizzNorBuzz(@Fact("number") Integer number) {
            return number % 5 != 0 || number % 7 != 0;
        }

        @Action
        public void printInput(@Fact("number") Integer number) {
            System.out.print(number+"\n");
        }
    }
}

  • 然后客户端调用
public class RuleJavaClient {
    public static void main(String[] args) {
        // 创建规则引擎
        RulesEngineParameters parameters = new RulesEngineParameters().skipOnFirstAppliedRule(true);
        RulesEngine fizzBuzzEngine = new DefaultRulesEngine(parameters);

        // 创建规则集并注册规则
        Rules rules = new Rules();
        rules.register(new RuleClass.FizzRule());
        rules.register(new RuleClass.BuzzRule());
        rules.register(new RuleClass.FizzBuzzRule(new RuleClass.FizzRule(), new RuleClass.BuzzRule()));
        rules.register(new RuleClass.NonFizzBuzzRule());

        // 执行规则
        Facts facts = new Facts();
        for (int i = 1; i <= 100; i++) {
            facts.put("number", i);
            fizzBuzzEngine.fire(rules, facts);
            System.out.println();
        }
    }
}

(2) yml 方式

  • resources 目录下新建 fizzbuzz.yml
---
name: "fizz rule"
description: "print fizz if the number is multiple of 5"
priority: 1
condition: "number % 5 == 0"
actions:
- "System.out.println(\"fizz\")"

---
name: "buzz rule"
description: "print buzz if the number is multiple of 7"
priority: 2
condition: "number % 7 == 0"
actions:
- "System.out.println(\"buzz\")"

---
name: "fizzbuzz rule"
description: "print fizzbuzz if the number is multiple of 5 and 7"
priority: 0
condition: "number % 5 == 0 && number % 7 == 0"
actions:
- "System.out.println(\"fizzbuzz\")"

---
name: "non fizzbuzz rule"
description: "print the number itself otherwise"
priority: 3
condition: "number % 5 != 0 || number % 7 != 0"
actions:
- "System.out.println(number)"
  • 客户端调用
public class RuleYmlClient {
    public static void main(String[] args) throws FileNotFoundException {
        // create a rules engine
        RulesEngineParameters parameters = new RulesEngineParameters().skipOnFirstAppliedRule(true);
        RulesEngine fizzBuzzEngine = new DefaultRulesEngine(parameters);

        // create rules
        Rules rules = MVELRuleFactory.createRulesFrom(new FileReader("fizzbuzz.yml"));

        // fire rules
        Facts facts = new Facts();
        for (int i = 1; i <= 100; i++) {
            facts.put("number", i);
            fizzBuzzEngine.fire(rules, facts);
            System.out.println();
        }
    }
}
aviator规则引擎配置是指在使用aviator规则引擎的过程中,对其进行相关参数的设置和调整,以适应具体业务的需求和实际场景的要求。 配置aviator规则引擎通常包括以下几个方面: 1. 规则文件的加载:在使用aviator规则引擎之前,需要将规则文件加载到引擎中。规则文件可以是文本文件,也可以是数据库中的数据。通过适当的配置,可以指定规则文件的路径或者连接信息,使得引擎能够正确地加载和解析规则文件。 2. 规则变量的设置:规则引擎需要获取和设置一系列的规则变量,以进行规则的计算和推理。在配置引擎时,可以指定规则变量的名称、类型和初值,以及变量的作用域和可见性等。这样,在使用规则引擎时可以直接引用规则变量,进行相应的计算和判断。 3. 规则优先级的设定:规则引擎通常会包含多条规则,而这些规则有时候会有互相之间的冲突。通过设定规则的优先级,可以确定规则执行的先后顺序,从而在执行过程中遵循预期的逻辑。一般来说,可以通过给规则设置权重或者指定执行顺序的方式来设定规则的优先级。 4. 规则引擎选项的调整:aviator规则引擎通常提供了一系列的配置选项,用于控制规则的执行模式和行为。比如,可以通过设置最大执行时间、最大递归深度、启用缓存等选项来控制规则的执行效率和安全性。此外,还可以针对debug模式、编译模式和并发模式等方面进行相应的设置。 通过合理的配置,aviator规则引擎可以在各种复杂的场景下发挥出强大的计算和决策能力,提高系统的效率和灵活性。但是需要注意的是,配置规则引擎要根据具体的需求和条件,避免过度的调整和复杂的配置,以免引起不必要的性能开销或者错误。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值