编程开源技术交流,分享技术与知识

网站首页 > 开源技术 正文

系统监控和维护进阶一之轻量级Java表达式引擎 Aviator

wxchong 2024-06-21 14:15:28 开源技术 12 ℃ 0 评论

1、前言

要开发一个高可用系统,对系统的各项性能指标进行监控报警是必不可少的,即系统性能指标满足一定阈值则报警提示。在开发和配置监控指标中必然会使用到规则引擎,主要的规则引擎包括:Drools,EasyRules,Aviator,URule,阿里规则引擎 QLExpress,Groovy等,这里我们先介绍Aviator的一些入门知识。

2、简介

Aviator是一个高性能、轻量级的Java语言实现的表达式求值引擎,主要用于各种表达式的动态求值。现在已经有很多开源可用的Java表达式求值引擎,为什么还需要Aviator呢?

Aviator的设计目标是轻量级和高性能 ,相比于Groovy、JRuby的笨重,Aviator非常小,加上依赖包也才450K,不算依赖包的话只有70K;当然,Aviator的语法是受限的,它不是一门完整的语言,而只是语言的一小部分集合。

其次,Aviator的实现思路与其他轻量级的求值器很不相同,其他求值器一般都是通过解释的方式运行,而Aviator则是直接将表达式编译成Java字节码,交给JVM去执行。简单来说,Aviator的定位是介于Groovy这样的重量级脚本语言和IKExpression这样的轻量级表达式引擎之间。

官网地址:GitHub - Grrui/aviator

3、Aviator的特性

  • 支持大部分运算操作符,包括算术操作符、关系运算符、逻辑操作符、位运算符、正则匹配操作符(=~)、三元表达式?: ,并且支持操作符的优先级和括号强制优先级,具体请看后面的操作符列表。
  • 支持大整数和精度运算(2.3.0版本引入)
  • 支持函数调用和自定义函数
  • 内置支持正则表达式匹配,类似Ruby、Perl的匹配语法,并且支持类Ruby的$digit指向匹配分组。
  • 自动类型转换,当执行操作的时候,会自动判断操作数类型并做相应转换,无法转换即抛异常。
  • 支持传入变量,支持类似a.b.c的嵌套变量访问。
  • 函数式风格的seq库,操作集合和数组
  • 性能优秀

4、Maven依赖

<dependency>
	<groupId>com.googlecode.aviator</groupId>
	<artifactId>aviator</artifactId>
	<version>5.2.7</version>
</dependency>

5、使用

5.1、执行表达式

Aviator的使用方式比较简单,通过使用AviatorEvaluator.execute基本可以实现大部分功能:

public class AviatorTest {
    public static void main(String[] args) {
        Long result =(Long) AviatorEvaluator.execute("1*2*3");
        System.out.println(result);
    }
}

这里要注意一个问题,为什么我们的 1*2*3计算过后,要强制转换成Long类型?
因为Aviator只支持四种数字类型(2.3.0之后的版本):Long、Double、big int、decimal
理论上任何整数都将转换成Long(超过Long范围的,将自动转换为big int),任何浮点数都将转换为Double
以大写字母N为后缀的整数都被认为是big int,如1N,2N,9999999999999999999999N等,都是big int类型。
以大写字母M为后缀的数字都被认为是decimal,如1M,2.222M, 100000.9999M等等,都是decimal类型。

5.2、使用变量

变量的使用有两种方法:execute()、exec();

execute(),需要传递Map格式参数

exec(),不需要传递Map

5.2.1、execute

public class AviatorTest {
    public static void main(String[] args) {
        Map<String, Object> data = Maps.newHashMap();
        data.put("username", "yyp");
        Object result = AviatorEvaluator.execute("'hello:' + username +'!'", data);
        System.out.println(result);
    }
}

5.2.2、exec

public class AviatorTest {
    public static void main(String[] args) {
        String username = "yyp";
        Object result = AviatorEvaluator.exec("'hello:' + username +'!'", username);
        System.out.println(result);
    }
}

5.3、使用函数

Aviator可以使用两种函数:内置函数、自定义函数

5.3.1、使用内置函数

Aviator提供了非常多的内置函数。具体可以查询官方文档参考。

public class AviatorTest {
    public static void main(String[] args) {
        // sysdate() 获取当前时间Date
        // date_to_string(date,format) date转为String
        System.out.println(AviatorEvaluator.execute("date_to_string(sysdate(),'yyyy-MM-dd HH:mm:ss')"));
    }
}

5.3.2、compile用法

public class AviatorTest {
    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", 10);
        env.put("b", 4);
        env.put("c", 5);
        Boolean result = (Boolean) compiledExp.execute(env);
        System.out.println(result);
    }
}

通过上面的代码片段可以看到,使用compile方法,先生成了 Expression 最后再由Expression.execute,然后传入参数 map,进行计算。
这么做的目的是,在我们实际使用过程中,很多情况下,我们要计算的公式都是一样的,只是每次的参数有所区别。
我们可以把一个编译好的Expression 缓存起来。这样每次可以直接获取我们之前编译的结果直接进行计算,避免Perm区OutOfMemory

Aviator本身自带一个全局缓存,如果决定缓存本次的编译结果,只需要

Expression compiledExp = AviatorEvaluator.compile(expression,true);

这样设置后,下一次编译同样的表达式,Aviator会从全局缓存中,拿出已经编译好的结果,不需要动态编译。如果需要使缓存失效,可以使用

AviatorEvaluator.invalidateCache(String expression);

5.3.3、自定义函数

自定义函数需要继承AbstractFunction类,重写目标方法。

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

    static class AddFunction extends AbstractFunction {
        @Override
        public String getName() {
            return "add";
        }

        @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);
        }
    }
}

我们看到:

  • 继承AbstractFunction类,就可以自定义一个函数
  • 重写call方法,就可以定义函数的逻辑,可以通过FunctionUtils获取脚本传递的参数
  • 通过getName可以设置函数的名称
  • 通过addFunction添加一个自定义函数类的实例,就可以注册函数
  • 最后就可以在Aviator的脚本里编译执行我们自定义的函数

总结

Aviator 表达式引擎是一个轻量级的 Java 表达式求值框架,具有高性能、易用性和可扩展性等特点。它可以在项目中高效地解析和计算表达式,并支持自定义函数和操作符的扩展。在实际应用中,合理利用 Aviator 的缓存机制、灵活使用函数和操作符等最佳实践,可以提高表达式求值的性能和可维护性。

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表