【Aviator】(一)初识 表达式引擎

推荐一个不错的AI教程: https://www.captainbed.net/blog-vincent/

 

一、关于"表达式引擎"

1.“表达式语言”(Expression Language):

“表达式语言(Expression Language),或称EL表达式,简称EL,是Java中的一种特殊的通用编程语言,借鉴于JavaScript和XPath。主要作用是在Java Web应用程序嵌入到网页(如JSP)中,用以访问页面的上下文以及不同作用域中的对象 ,取得对象属性的值,或执行简单的运算或判断操作。EL在得到某个数据时,会自动进行数据类型的转换。

语法为:

以“${”开始,以“}”作为结束:

${EL表达式}
获取某对象的值可以直接写入对象的名称,如获取对象名为“user”的对象的值:

${user}
获取某对象的属性的值使用点操作符(“.”操作符),如获取对象user的name属性和age属性的值的语法如下:

${user.name}
${user.age}

   java ee tutorial中的解释:

This chapter introduces the Expression Language (also referred to as the EL), which provides an important mechanism for enabling the presentation layer (web pages) to communicate with the application logic (managed beans). The EL is used by both JavaServer Faces technology and JavaServer Pages (JSP) technology. The EL represents a union of the expression languages offered by JavaServer Faces technology and JSP technology.

 

2.表达式引擎(Expression Engine)

        维基百科和google没有找到一个合适的说明,说说我的理解,表达式引擎,是实现“动态编码”的一种手段,类似线上产品的配置中心,可以动态地配置期望的值和表达式。

     

3.Aviator

     3.0 what is it?

     Aviator是一个轻量级、高性能的Java表达式执行引擎,它动态地将表达式编译成字节码并运行,主要用于各种表达式的动态求值。

     3.1 java语言中,Aviator同类产品有那些?

1.Fel(Fast Expression Language)是开放的、高效的、轻量级的表达式语言。拥有解释执行和编译执行双引擎。Fel在编译执行时,做了很多优化,适合处理海量数据。Fel扩展性强,用户可以定制Fel执行时的众多环节,以满足需求。Fel学习门槛非常低,基本上可以做到拿来即用,即使是二次开发,也非常简单。Fel基于Java1.5开发,适用于Java1.5及以上版本。
2.Spel(spring表达式语言)
3.mvel
4.QLexpress(阿里)  偏向于规则引擎
5.ikexpress(2009 停止维护)
6.ognl(不考虑,太老了)

     性能对比,参考如下:

    https://jindw.iteye.com/blog/732354 

    https://java-source.net/open-source/expression-languages 

    https://cloud.tencent.com/info/76108cd74290530f02dedfa8388e431f.html 

    https://blog.csdn.net/shichen2010/article/details/78466293 

     最终倾向于fel和aviator.

 

4.为什么选择aviator?

1)官方的说法:

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

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

2)我使用的体会:

    1.活跃度高,版本更新很快,

    2.有前人在金融方面的使用,并发情况下,execute方法线程安全。

    3.fel不支持乘方运算,需要结合spring的el表达式来进行。

 

二、Aviator应用场景

    1)表达式的动态求值

       金融项目中,产品试算、金额高精度的计算。之前的计算公式统一维护在base包(base包的公式会被订单、产品、api、财务、还款等系统依赖),由于公式会频繁地改动,造成base频繁升级,其他系统也得跟着发版,耗费人力。

 

三.aviator特性

支持绝大多数运算操作符,包括算术操作符、关系运算符、逻辑操作符、位运算符、正则匹配操作符(=~)、三元表达式(?:)
支持操作符优先级和括号强制设定优先级。
支持赋值。
逻辑运算符支持短路运算。
支持丰富类型,例如nil、整数和浮点数、字符串、正则表达式、日期、变量等,支持自动类型转换。
支持 lambda 匿名函数和闭包。
内置一套强大的常用函数库
可自定义函数,易于扩展
可重载操作符
支持大数运算(BigInteger)和高精度运算(BigDecimal)。
小巧并性能优秀

【Aviator】(一)初识 表达式引擎_第1张图片

 

四.应用

1.引包


  com.googlecode.aviator
  aviator
  {version}

 

2.aviator的代码示例:https://github.com/killme2008/aviator/tree/master/src/test/java/com/googlecode/aviator/example 

 

3.核心操作

0)核心功能类:com.googlecode.aviator.AviatorEvaluator,所有的计算都是通过这个类来执行,比如:

import com.googlecode.aviator.AviatorEvaluator;
public class TestAviator {
    public static void main(String[] args) {
        //执行1+2+3算数计算
        Long result = (Long) AviatorEvaluator.execute("1+2+3");
        System.out.println(result);
    }
}

  此处的yourName会被替换执行,输出在result中,金额计算,就是这个原理,只不过提前compile编译好表达式,参考后续介绍。

 

1)使用变量

      “变量”的应用,是Aviator用于表达式动态求值最普遍的用法,如下demo所示:

public class TestAviator {
    public static void main(String[] args) {
        //todo vincent 
        //此处的yourName会被替换执行,输出在result中,金额计算,就是这个原理,只不过提前compile编译好表达式,参考后续介绍。
        String yourName = "Michael"; 
        Map env = new HashMap();
        env.put("yourName", yourName);
        String result = (String) AviatorEvaluator.execute(" 'hello ' + yourName ", env);
        System.out.println(result);  // hello Michael
    }
}

 

2)exec和execute方法

 如1)中所述,通过execute方法实现了“变量替换”,前提变量要存放到Map中,如果不想放到map中呢?参考下述demo的test02:

public static void main(String[] args) {
    String yourName = "Michael";
    //test01
    Map env = new HashMap();
    env.put("yourName", yourName);
    String result1 = (String) AviatorEvaluator.execute(" 'hello ' + yourName ", env);
    System.out.println(result1);

    //test02
    String result2 = (String) AviatorEvaluator.execute(" 'hello '" + yourName);
    System.out.println(result2);
}

报错信息如下:

com.googlecode.aviator.exception.ExpressionSyntaxErrorException: Syntax error:Unexpect token 'Michael' at 9, current token: [type='variable',lexeme='Michael',index=9]. Parsing expression: ` 'hello 'Michael^^`

变量若不放到Map中,则需要通过调用exec方法实现,如下:

String name = "vincent";
AviatorEvaluator.exec(" 'hello ' + yourName ", name); // hello vincent

3)编译表达式(更推荐的使用方式)

        上述介绍aviator时,可知“aviator动态地将表达式编译成字节码并运行”,即前边的demo,都是aviator背后都帮你做了编译并执行的工作。 同时你可以自己先编译表达式, 返回一个编译的结果, 然后传入不同的env来复用编译结果, 提高性能, 这是更推荐的使用方式,我们项目中采用的,也是这种形式。

public class TestAviator {
    public static void main(String[] args) {
        String expression = "a-(b-c)>100";
        // 编译表达式
        Expression compiledExp = AviatorEvaluator.compile(expression);
        Map 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的中间对象, 当要执行表达式的时候传入env并调用Expression的execute方法即可。 表达式中使用了括号来强制优先级, 这个例子还使用了>用于比较数值大小, 比较运算符!=、==、>、>=、<、<=不仅可以用于数值, 也可以用于String、Pattern、Boolean等等, 甚至是任何用户传入的两个都实现了java.lang.Comparable接口的对象之间。

      同时官方说明,编译好的表达式可以存入缓存,如下:

public static Expression compile(String expression, boolean cached)

      根据我的使用经验,对于分布式项目,且公式内容本身经常会变化,建议不要存储到本地缓存,可以使用redis做公式缓存。

 

4)三元操作符

AviatorEvaluator.exec("a>0? 'yes':'no'", 1);  // yes

公式的要求若是这样,适合三元操作符:

irr!=0?(termVehiclePayment-tailPaymentAmount-discountAmount)*(irr/12*repaymentPeriod)/(math.pow((1+irr/12*repaymentPeriod),billTerm)-1)+(termVehiclePayment-discountAmount)*(irr/12*repaymentPeriod):(termVehiclePayment-tailPaymentAmount-discountAmount)/billTerm

 

5)大数计算和精度

       从 2.3.0 版本开始,aviator 开始支持大数字计算和特定精度的计算, 本质上就是支持java.math.BigInteger和java.math.BigDecimal两种类型, 这两种类型在 aviator 中简称 为big int和decimal类型。 类似99999999999999999999999999999999这样的数字在 Java 语言里是没办法编译通过 的, 因为它超过了Long类型的范围, 只能用BigInteger来封装。但是 aviator 通过包装,可 以直接支持这种大整数的计算,例如:

public static void main(String[] args)
{
    System.out.println(AviatorEvaluator.exec("99999999999999999999999999999999 + 99999999999999999999999999999999"));
}

        big int和decimal的运算,跟其他数字类型long,double没有什么区别,操作符仍然是一样的。 aviator重载了基本算术操作符来支持这两种新类型:

public static void main(String[] args) {
    Object rt = AviatorEvaluator.exec("9223372036854775807100.356M * 2");
    System.out.println(rt + " " + rt.getClass());  // 18446744073709551614200.712 class java.math.BigDecimal
    rt = AviatorEvaluator.exec("92233720368547758074+1000");
    System.out.println(rt + " " + rt.getClass());  // 92233720368547759074 class java.math.BigInteger
    BigInteger a = new BigInteger(String.valueOf(Long.MAX_VALUE) + String.valueOf(Long.MAX_VALUE));
    BigDecimal b = new BigDecimal("3.2");
    BigDecimal c = new BigDecimal("9999.99999");
    rt = AviatorEvaluator.exec("a+10000000000000000000", a);
    System.out.println(rt + " " + rt.getClass());  // 92233720368547758089223372036854775807 class java.math.BigInteger
    rt = AviatorEvaluator.exec("b+c*2", b, c);
    System.out.println(rt + " " + rt.getClass());  // 20003.19998 class java.math.BigDecimal
    rt = AviatorEvaluator.exec("a*b/c", a, b, c);
    System.out.println(rt + " " + rt.getClass());  // 2.951479054745007313280155218459508E+34 class java.math.BigDecimal
}

       当big int或者decimal和其他类型的数字做运算的时候,按照long < big int < decimal < double的规则做提升, 也就是说运算的数字如果类型不一致, 结果的类型为两者之间更“高”的类型。

 

6)调用函数,丰富的内置函数

     https://github.com/killme2008/aviator/wiki/%E5%86%85%E7%BD%AE%E5%87%BD%E6%95%B0 

7)选项设置

AviatorEvaluator.setOption()

1.运行模式(优化级别)

Options.OPTIMIZE_LEVEL
AviatorEvaluator.EVAL (运行时的性能优先,适合长期运行的表达式)
AviatorEvaluator.COMPILE (编译的性能优先,适合需要频繁编译表达式的场景)

2.调试信息
Options.TRACE
是否跟踪运行,打开后将在控制台打印整个表达式的求值过程。请勿在生产环境打开,将极大地降低性能。默认为 false 关闭。

3.精度调整

        AviatorEvaluator.setOption(Options.MATH_CONTEXT, MathContext.DECIMAL64);

        Decimal 数字类型的运算精度,默认是 java.util.MathContext.DECIMAL128。

       Options.ALWAYS_PARSE_FLOATING_POINT_NUMBER_INTO_DECIMAL

       是否将所有浮点数解析为 Decimal 类型,适合需要高精度运算的场景,并且不想为每个浮点数字指定 M 后缀(表示 Decimal 类型)。默认为 false 不开启。

 

写在最后:

        Aviator实现了动态表达式的功能,但局限在于,如果公式本身中有逻辑代码,它不是那么灵活,如果需要把部分代码和表达式同时抽离,可以参考“java脚本引擎”。

 

     That's all.

 

 

 

 

 

 

 

 

 

 

 

 

你可能感兴趣的:(【Aviator】(一)初识 表达式引擎)