遇到的問題
最近項(xiàng)目中有這樣一種場景:需要改變部分訂單的結(jié)算方式,這個(gè)改動點(diǎn)對交易結(jié)算影響很大,需要逐步切流以減少風(fēng)險(xiǎn)。訂單有buyerId(買家id)、sellerId(賣家id)、tkBizTag(訂單打標(biāo))……幾十個(gè)字段,如果case by case硬編碼來限定切流的場景來做,就很不靈活,單純這個(gè)切流就要上多次線。
因此有這樣的技術(shù)需求:使用一種靈活多變的切流方式,即可支持對按照訂單對象任何一個(gè)參數(shù)滿足某種條件時(shí)進(jìn)行切流,如按照訂單類型字段、某些買家id符合要求。
解決方案
經(jīng)過調(diào)研,最終采用aviator表達(dá)式+動態(tài)內(nèi)容推送中間件(diamond)來實(shí)現(xiàn)。
一個(gè)簡單的demo如下:
NCpsPaymentDTO paymentDTO = newNCpsPaymentDTO();
paymentDTO.setTkBizTag(5);
paymentDTO.setTbBuyerId(1234L);
ExtraInfo extraInfo = new ExtraInfo();
extraInfo.setEventId(1234567L);
HashMap paramMap= new HashMap();
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取自動態(tài)內(nèi)容推送中間件diamond,可以根據(jù)需求隨時(shí)更新并推送到各臺線上機(jī)器。
了解到這個(gè)程度足夠了么?No.關(guān)于aviator還需要知道得更多。
Aviator簡介
Aviator是一個(gè)高性能、輕量級的java語言實(shí)現(xiàn)的表達(dá)式求值引擎,主要用于各種表達(dá)式的動態(tài)求值。現(xiàn)在已經(jīng)有很多開源可用的java表達(dá)式求值引擎,為什么還需要Avaitor呢?
Aviator的設(shè)計(jì)目標(biāo)是輕量級和高性能,相比于Groovy、JRuby的笨重,Aviator非常小,加上依賴包也才450K,不算依賴包的話只有70K;當(dāng)然,Aviator的語法是受限的,它不是一門完整的語言,而只是語言的一小部分集合。
其次,Aviator的實(shí)現(xiàn)思路與其他輕量級的求值器很不相同,其他求值器一般都是通過解釋的方式運(yùn)行,而Aviator則是直接將表達(dá)式編譯成Java字節(jié)碼,交給JVM去執(zhí)行。簡單來說,Aviator的定位是介于Groovy這樣的重量級腳本語言和IKExpression這樣的輕量級表達(dá)式引擎之間。
Aviator的特性
- 支持大部分運(yùn)算操作符,包括算術(shù)操作符、關(guān)系運(yùn)算符、邏輯操作符、正則匹配操作符(=~)、三元表達(dá)式?:,并且支持操作符的優(yōu)先級和括號強(qiáng)制優(yōu)先級,具體請看后面的操作符列表。
- 支持函數(shù)調(diào)用和自定義函數(shù)
- 支持正則表達(dá)式匹配,類似Ruby、Perl的匹配語法,并且支持類Ruby的$digit指向匹配分組。
- 自動類型轉(zhuǎn)換,當(dāng)執(zhí)行操作的時(shí)候,會自動判斷操作數(shù)類型并做相應(yīng)轉(zhuǎn)換,無法轉(zhuǎn)換即拋異常。
- 支持傳入變量,支持類似a.b.c的嵌套變量訪問。
- 性能優(yōu)秀
Aviator的限制
- 沒有if else、do while等語句,沒有賦值語句,沒有位運(yùn)算符
- 僅支持邏輯表達(dá)式、算術(shù)表達(dá)式、三元表達(dá)式和正則匹配
Aviator用法
最新jar包
<dependency>
<groupId>com.googlecode.aviator</groupId>
<artifactId>aviator</artifactId>
<version>2.3.4</version>
</dependency>
算術(shù)表達(dá)式
Long result = (Long)AviatorEvaluator.execute("1+2+3");
System.out.println(result);//6
note:Aviator的數(shù)值類型僅支持Long和Double,任何整數(shù)都將轉(zhuǎn)換成Long,任何浮點(diǎn)數(shù)都將轉(zhuǎn)換為Double,包括用戶傳入的變量數(shù)值。
邏輯表達(dá)式
Boolean result2 = (Boolean)AviatorEvaluator.execute("3>1 && 2!=4 || true");
System.out.println(result2);//true
變量和字符串相加
Map env = newHashMap();
env.put("yourname","aviator");
String result3 = (String)AviatorEvaluator.execute(" 'hello ' + yourname ", env);
System.out.println(result3);
上面的例子演示了怎么向表達(dá)式傳入變量值,表達(dá)式中的yourname是一個(gè)變量,默認(rèn)為null,通過傳入Map的變量綁定環(huán)境,將yourname設(shè)置為你輸入的名稱。env的key是變量名,value是變量的值。
Aviator 2.2開始新增加一個(gè)exec方法,可以更方便地傳入變量并執(zhí)行,而不需要構(gòu)造env這個(gè)map了:
String result4= (String) AviatorEvaluator.exec(" 'hello ' + yourname ","aviator2");
System.out.println(result4);
三元表達(dá)式
String result5=(String)AviatorEvaluator.execute("3>0? 'yes':'no'");
System.out.println(result5);
函數(shù)調(diào)用
AviatorEvaluator.execute("string.length('hello')"); //求字符串長度
AviatorEvaluator.execute("string.contains('hello','h')"); //判斷字符串是否包含字符串AviatorEvaluator.execute("string.startsWith('hello','h')"); //是否以子串開頭AviatorEvaluator.execute("string.endsWith('hello','llo')");是否以子串結(jié)尾
AviatorEvaluator.execute("math.pow(-3,2)"); //求n次方
AviatorEvaluator.execute("math.sqrt(14.0)"); //開 平方根
AviatorEvaluator.execute("math.sin(20)"); //正弦函數(shù)