zoukankan      html  css  js  c++  java
  • Aviator——轻量级Java表达式求值引擎

    简介

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

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

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


    Aviator的特性

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

    Aviator的限制

    • 没有if else、do while等语句,没有赋值语句,仅支持逻辑表达式、算术表达式、三元表达式和正则匹配。
    • 不支持八进制数字字面量,仅支持十进制和十六进制数字字面量。

    Jar包引入

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

    让Aviator动起来

    既然Google帮我们做了那么多工作,那么怎么让他动起来呢?

    Aviator有一个统一执行的入口 AviatorEvaluator 类。

    他有一系列的静态方法提供给我们使用。

    我们主要用到的两种方法为

    AviatorEvaluator.compile
    AviatorEvaluator.execute

    execute用法

    先来看一个execute的最简单示例

    import com.googlecode.aviator.AviatorEvaluator;
    
    
    public class AviatorSimpleExample {
        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类型。

    如果都是上图中,最基础的这种数字计算,肯定不可能满足各种业务场景。
    下面介绍一下传入变量

    import com.googlecode.aviator.AviatorEvaluator;
    
    import java.util.HashMap;
    import java.util.Map;
    
    public class AviatorSimpleExample {
        public static void main(String[] args) {
            String name = "JACK";
            Map<String,Object> env = new HashMap<>();
            env.put("name", name);
            String result = (String) AviatorEvaluator.execute(" 'Hello ' + name ", env);
            System.out.println(result);
        }
    }
    输出结果: Hello JACK

    介绍一下Aviator的内置函数用法,其实特别简单,只要按照函数列表中(最下方有函数列表)定义的内容,直接使用就可以

    import com.googlecode.aviator.AviatorEvaluator;
    
    import java.util.HashMap;
    import java.util.Map;
    
    public class AviatorSimpleExample {
        public static void main(String[] args) {
            String str = "使用Aviator";
            Map<String,Object> env = new HashMap<>();
            env.put("str",str);
            Long length = (Long)AviatorEvaluator.execute("string.length(str)",env);
            System.out.println(length);
        }
    }
    输出结果 : 14

    compile用法

    import com.googlecode.aviator.AviatorEvaluator;
    import com.googlecode.aviator.Expression;
    
    import java.util.HashMap;
    import java.util.Map;
    
    public class AviatorSimpleExample {
        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);
        }
    }
    输出结果 false

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

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

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

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

    AviatorEvaluator.invalidateCache(String expression);

    自定义函数的使用

    import com.googlecode.aviator.AviatorEvaluator;
    import com.googlecode.aviator.Expression;
    import com.googlecode.aviator.runtime.function.AbstractFunction;
    import com.googlecode.aviator.runtime.function.FunctionUtils;
    import com.googlecode.aviator.runtime.type.AviatorBigInt;
    import com.googlecode.aviator.runtime.type.AviatorObject;
    
    import java.util.HashMap;
    import java.util.Map;
    
    public class AviatorSimpleExample {
        public static void main(String[] args) {
            AviatorEvaluator.addFunction(new MinFunction());
            String expression = "min(a,b)";
            Expression compiledExp = AviatorEvaluator.compile(expression, true);
            Map<String, Object> env = new HashMap<>();
            env.put("a", 100.3);
            env.put("b", 45);
            Double result = (Double) compiledExp.execute(env);
            System.out.println(result);
        }
    
        static class MinFunction extends AbstractFunction {
            @Override public AviatorObject call(Map<String, Object> env, AviatorObject arg1, AviatorObject arg2) {
                Number left = FunctionUtils.getNumberValue(arg1, env);
                Number right = FunctionUtils.getNumberValue(arg2, env);
                return new AviatorBigInt(Math.min(left.doubleValue(), right.doubleValue()));
            }
    
            public String getName() {
                return "min";
            }
        }
    }

    从实际业务中使用的自定义函数来举例
    我们需要对比两个数字的大小
    (因为实际业务中,这两个数字为多个表达式计算的结果,如果不写自定函数,只能使用?:表达式来进行计算,会显得异常凌乱)
    我们定义了一个 MinFunction 继承自 AbstractFunction
    实现具体的方法,返回我们想要的结果。
    务必要实现 getName这个方法,用于定义我们函数在Aviator中使用的名字。
    在执行compile之前,先把我们的函数Add到Aviator函数列表中,后就可以使用了。
    此处输出结果为

    45.0

    内置函数列表


    操作符列表

    序号操作符结合性操作数限制
    0 () [ ] 从左到右 ()用于函数调用,[ ]用于数组和java.util.List的元素访问,要求[indx]中的index必须为整型
    1 ! - ~ 从右到左 ! 能用于Boolean,- 仅能用于Number,~仅能用于整数
    2 * / % 从左到右 Number之间
    3 + - 从左到右 + - 都能用于Number之间, + 还能用于String之间,或者String和其他对象
    4 << >> >>> 从左到右 仅能用于整数
    5 < <= > >= 从左到右 Number之间、String之间、Pattern之间、变量之间、其他类型与nil之间
    6 == != =~ 从左到右 ==和!=作用于Number之间、String之间、Pattern之间、变量之间、其他类型与nil之间以及String和java.util.Date之间,=~仅能作用于String和Pattern之间
    7 & 从左到右 整数之间
    8 ^ 从左到右 整数之间
    9 ¦ 从左到右 整数之间
    10 && 从左到右 Boolean之间,短路
    11 ¦¦ 从左到右 Boolean之间,短路
    12 ? : 从右到左 第一个操作数的结果必须为Boolean,第二和第三操作数结果无限制

    内置函数

    函数名称说明
    sysdate() 返回当前日期对象java.util.Date
    rand() 返回一个介于0-1的随机数,double类型
    print([out],obj) 打印对象,如果指定out,向out打印,否则输出到控制台
    println([out],obj) 与print类似,但是在输出后换行
    now() 返回System.currentTimeMillis
    long(v) 将值的类型转为long
    double(v) 将值的类型转为double
    str(v) 将值的类型转为string
    date_to_string(date,format) 将Date对象转化化特定格式的字符串,2.1.1新增
    string_to_date(source,format) 将特定格式的字符串转化为Date对象,2.1.1新增
    string.contains(s1,s2) 判断s1是否包含s2,返回Boolean
    string.length(s) 求字符串长度,返回Long
    string.startsWith(s1,s2) s1是否以s2开始,返回Boolean
    string.endsWith(s1,s2) s1是否以s2结尾,返回Boolean
    string.substring(s,begin[,end]) 截取字符串s,从begin到end,end如果忽略的话,将从begin到结尾,与java.util.String.substring一样。
    string.indexOf(s1,s2) java中的s1.indexOf(s2),求s2在s1中的起始索引位置,如果不存在为-1
    string.split(target,regex,[limit]) Java里的String.split方法一致,2.1.1新增函数
    string.join(seq,seperator) 将集合seq里的元素以seperator为间隔连接起来形成字符串,2.1.1新增函数
    string.replace_first(s,regex,replacement) Java里的String.replaceFirst 方法,2.1.1新增
    string.replace_all(s,regex,replacement) Java里的String.replaceAll方法 ,2.1.1新增
    math.abs(d) 求d的绝对值
    math.sqrt(d) 求d的平方根
    math.pow(d1,d2) 求d1的d2次方
    math.log(d) 求d的自然对数
    math.log10(d) 求d以10为底的对数
    math.sin(d) 正弦函数
    math.cos(d) 余弦函数
    math.tan(d) 正切函数
    map(seq,fun) 将函数fun作用到集合seq每个元素上,返回新元素组成的集合
    filter(seq,predicate) 将谓词predicate作用在集合的每个元素上,返回谓词为true的元素组成的集合
    count(seq) 返回集合大小
    include(seq,element) 判断element是否在集合seq中,返回boolean值
    sort(seq) 排序集合,仅对数组和List有效,返回排序后的新集合
    reduce(seq,fun,init) fun接收两个参数,第一个是集合元素,第二个是累积的函数,本函数用于将fun作用在集合每个元素和初始值上面,返回最终的init值
    seq.eq(value) 返回一个谓词,用来判断传入的参数是否跟value相等,用于filter函数,如filter(seq,seq.eq(3)) 过滤返回等于3的元素组成的集合
    seq.neq(value) 与seq.eq类似,返回判断不等于的谓词
    seq.gt(value) 返回判断大于value的谓词
    seq.ge(value) 返回判断大于等于value的谓词
    seq.lt(value) 返回判断小于value的谓词
    seq.le(value) 返回判断小于等于value的谓词
    seq.nil() 返回判断是否为nil的谓词
    seq.exists() 返回判断不为nil的谓词

    常量和变量

    说明
    true 真值
    false 假值
    nil 空值
    $digit 正则表达式匹配成功后的分组,$0表示匹配的字符串,$1表示第一个分组 etc.

    参考资料


    https://code.google.com/archive/p/aviator/wikis/User_Guide_zh.wiki?spm=a2c4e.10696291.0.0.682219a4bhEzuL&file=User_Guide_zh.wiki

  • 相关阅读:
    阿里消息队列中间件 RocketMQ 源码分析 —— Message 拉取与消费(上)
    数据库中间件 ShardingJDBC 源码分析 —— SQL 解析(三)之查询SQL
    数据库分库分表中间件 ShardingJDBC 源码分析 —— SQL 解析(六)之删除SQL
    数据库分库分表中间件 ShardingJDBC 源码分析 —— SQL 解析(五)之更新SQL
    消息队列中间件 RocketMQ 源码分析 —— Message 存储
    源码圈 300 胖友的书单整理
    数据库分库分表中间件 ShardingJDBC 源码分析 —— SQL 路由(一)分库分表配置
    数据库分库分表中间件 ShardingJDBC 源码分析 —— SQL 解析(四)之插入SQL
    数据库分库分表中间件 ShardingJDBC 源码分析 —— SQL 路由(二)之分库分表路由
    C#中Math类的用法
  • 原文地址:https://www.cnblogs.com/kaleidoscope/p/13132315.html
Copyright © 2011-2022 走看看