使用aviator表达式进行动态切流

 遇到的问题

最近项目中有这样一种场景:

需要改变部分订单的结算方式,这个改动点对交易结算影响很大,需要逐步切流以减少风险。

如果采用case by case硬编码限定切流的场景来做,就很不灵活,单纯这个切流就要上多次线。
因此有这样的技术需求:使用一种灵活多变的切流方式,即可支持对按照订单对象任何一个参数满足某种条件时进行切流,如按照订单类型字段、某些买家id符合要求。

 

解决方案

经过调研,最终采用aviator表达式来实现(辅以动态内容推送中间件diamond)。

一个简单的demo便可满足上述需求。       

NCpsPaymentDTO paymentDTO = newNCpsPaymentDTO();

        paymentDTO.setTkBizTag(5);

        paymentDTO.setTbBuyerId(1234L);

        ExtraInfo extraInfo = new ExtraInfo();

        extraInfo.setEventId(1234567L);

        HashMap<String, Object> paramMap= new HashMap<String, Object>();

        //这里赋值确保非null,保证配置的表达式可以不用再判空

        paramMap.put("paymentDTO",paymentDTO);

        paramMap.put("extraInfo",extraInfo);



        String configInfo ="paymentDTO.tkBizTag == 5 && paymentDTO.tbBuyerId % 10000 <=2000 && extraInfo.eventId == 1234567";

        Expression expression =AviatorEvaluator.compile(configInfo);

        Boolean rst = (Boolean)expression.execute(paramMap);

        System.out.println(rst); //true

 

Note:

其中configInfo取自动态内容推送中间件diamond。

 

了解到这个程度足够了么?No.关于aviator还需要知道得更多。

 

Aviator简介

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

 

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

 

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

 

Aviator的特性

支持大部分运算操作符,包括算术操作符、关系运算符、逻辑操作符、正则匹配操作符(=~)、三元表达式?:,并且支持操作符的优先级和括号强制优先级,具体请看后面的操作符列表。

支持函数调用和自定义函数

支持正则表达式匹配,类似Ruby、Perl的匹配语法,并且支持类Ruby的$digit指向匹配分组。

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

支持传入变量,支持类似a.b.c的嵌套变量访问。

性能优秀

 

Aviator的限制

没有if else、do while等语句,没有赋值语句,仅支持逻辑表达式、算术表达式、三元表达式和正则匹配。

没有位运算符

 

最新jar包

            <dependency>

               <groupId>com.googlecode.aviator</groupId>

               <artifactId>aviator</artifactId>

               <version>2.3.3</version>

            </dependency>

 

 

Aviator用法

 

算术表达式     

   Long result = (Long)AviatorEvaluator.execute("1+2+3");

        System.out.println(result); //6

note:Aviator的数值类型仅支持Long和Double,任何整数都将转换成Long,任何浮点数都将转换为Double,包括用户传入的变量数值。

 

逻辑表达式 

       Boolean result2 = (Boolean)AviatorEvaluator.execute("3>1 && 2!=4 || true");

        System.out.println(result2); //true

 

变量和字符串相加     

  Map<String, Object> env = newHashMap<String, Object>();

        env.put("yourname","aviator");

        String result3 = (String)AviatorEvaluator.execute(" 'hello ' + yourname ", env);

        System.out.println(result3);

上面的例子演示了怎么向表达式传入变量值,表达式中的yourname是一个变量,默认为null,通过传入Map<String,Object>的变量绑定环境,将yourname设置为你输入的名称。env的key是变量名,value是变量的值。

 

Aviator 2.2开始新增加一个exec方法,可以更方便地传入变量并执行,而不需要构造env这个map了:
       

 String result4 = (String) AviatorEvaluator.exec(" 'hello ' + yourname ","aviator2");
 System.out.println(result4);

三元表达式

String result5 =(String)AviatorEvaluator.execute("3>0? 'yes':'no'"); 
System.out.println(result5);

 

函数调用

AviatorEvaluator.execute("string.length('hello')");    //求字符串长度
AviatorEvaluator.execute("string.contains('hello','h')");  //判断字符串是否包含字符串AviatorEvaluator.execute("string.startsWith('hello','h')");  //是否以子串开头AviatorEvaluator.execute("string.endsWith('hello','llo')"); 是否以子串结尾
AviatorEvaluator.execute("math.pow(-3,2)");   // 求n次方
AviatorEvaluator.execute("math.sqrt(14.0)");   //开 平方根
AviatorEvaluator.execute("math.sin(20)");    //正弦函数

 

还有一些更细致的用法,详情可以参考官方文档:https://github.com/killme2008/aviator/wiki

 参考文章
http://www.blogjava.net/killme2008/archive/2010/06/29/324758.html

http://blog.csdn.net/keda8997110/article/details/50782848 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章