遇到的問(wèn)題
最近項(xiàng)目中有這樣一種場(chǎng)景:需要改變部分訂單的結(jié)算方式,這個(gè)改動(dòng)點(diǎn)對(duì)交易結(jié)算影響很大壹置,需要逐步切流以減少風(fēng)險(xiǎn)竞思。訂單有buyerId(買家id)、sellerId(賣家id)钞护、tkBizTag(訂單打標(biāo))……幾十個(gè)字段盖喷,如果case by case硬編碼來(lái)限定切流的場(chǎng)景來(lái)做,就很不靈活难咕,單純這個(gè)切流就要上多次線课梳。
因此有這樣的技術(shù)需求:使用一種靈活多變的切流方式,即可支持對(duì)按照訂單對(duì)象任何一個(gè)參數(shù)滿足某種條件時(shí)進(jìn)行切流步藕,如按照訂單類型字段惦界、某些買家id符合要求。
解決方案
經(jīng)過(guò)調(diào)研咙冗,最終采用aviator表達(dá)式+動(dòng)態(tài)內(nèi)容推送中間件(diamond)來(lái)實(shí)現(xiàn)沾歪。
一個(gè)簡(jiǎn)單的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取自動(dòng)態(tài)內(nèi)容推送中間件diamond,可以根據(jù)需求隨時(shí)更新并推送到各臺(tái)線上機(jī)器雾消。
了解到這個(gè)程度足夠了么灾搏?No.關(guān)于aviator還需要知道得更多。
Aviator簡(jiǎn)介
Aviator是一個(gè)高性能立润、輕量級(jí)的java語(yǔ)言實(shí)現(xiàn)的表達(dá)式求值引擎狂窑,主要用于各種表達(dá)式的動(dòng)態(tài)求值。現(xiàn)在已經(jīng)有很多開(kāi)源可用的java表達(dá)式求值引擎桑腮,為什么還需要Avaitor呢泉哈?
Aviator的設(shè)計(jì)目標(biāo)是輕量級(jí)和高性能,相比于Groovy、JRuby的笨重丛晦,Aviator非常小奕纫,加上依賴包也才450K,不算依賴包的話只有70K;當(dāng)然烫沙,Aviator的語(yǔ)法是受限的匹层,它不是一門完整的語(yǔ)言,而只是語(yǔ)言的一小部分集合锌蓄。
其次升筏,Aviator的實(shí)現(xiàn)思路與其他輕量級(jí)的求值器很不相同,其他求值器一般都是通過(guò)解釋的方式運(yùn)行瘸爽,而Aviator則是直接將表達(dá)式編譯成Java字節(jié)碼您访,交給JVM去執(zhí)行。簡(jiǎn)單來(lái)說(shuō)蝶糯,Aviator的定位是介于Groovy這樣的重量級(jí)腳本語(yǔ)言和IKExpression這樣的輕量級(jí)表達(dá)式引擎之間洋只。
Aviator的特性
- 支持大部分運(yùn)算操作符辆沦,包括算術(shù)操作符昼捍、關(guān)系運(yùn)算符、邏輯操作符肢扯、正則匹配操作符(=~)妒茬、三元表達(dá)式?:,并且支持操作符的優(yōu)先級(jí)和括號(hào)強(qiáng)制優(yōu)先級(jí)蔚晨,具體請(qǐng)看后面的操作符列表乍钻。
- 支持函數(shù)調(diào)用和自定義函數(shù)
- 支持正則表達(dá)式匹配,類似Ruby铭腕、Perl的匹配語(yǔ)法银择,并且支持類Ruby的$digit指向匹配分組。
- 自動(dòng)類型轉(zhuǎn)換累舷,當(dāng)執(zhí)行操作的時(shí)候浩考,會(huì)自動(dòng)判斷操作數(shù)類型并做相應(yīng)轉(zhuǎn)換,無(wú)法轉(zhuǎn)換即拋異常被盈。
- 支持傳入變量析孽,支持類似a.b.c的嵌套變量訪問(wèn)。
- 性能優(yōu)秀
Aviator的限制
- 沒(méi)有if else只怎、do while等語(yǔ)句袜瞬,沒(méi)有賦值語(yǔ)句,沒(méi)有位運(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殿漠,通過(guò)傳入Map的變量綁定環(huán)境,將yourname設(shè)置為你輸入的名稱佩捞。env的key是變量名绞幌,value是變量的值。
Aviator 2.2開(kāi)始新增加一個(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')"); //求字符串長(zhǎng)度
AviatorEvaluator.execute("string.contains('hello','h')"); //判斷字符串是否包含字符串AviatorEvaluator.execute("string.startsWith('hello','h')"); //是否以子串開(kāi)頭AviatorEvaluator.execute("string.endsWith('hello','llo')");是否以子串結(jié)尾
AviatorEvaluator.execute("math.pow(-3,2)"); //求n次方
AviatorEvaluator.execute("math.sqrt(14.0)"); //開(kāi) 平方根
AviatorEvaluator.execute("math.sin(20)"); //正弦函數(shù)