簡介
Aviator是一個高性能、輕量級的java語言實現的表達式求值引擎,主要用於各種表達式的動態求值。現在已經有很多開源可用的java表達式求值引擎,為什么還需要Avaitor呢?
Aviator的設計目標是輕量級
和高性能
,相比於Groovy、JRuby的笨重,Aviator非常小,加上依賴包也才450K,不算依賴包的話只有70K;當然,Aviator的語法是受限的,它不是一門完整的語言,而只是語言的一小部分集合。
其次,Aviator的實現思路與其他輕量級的求值器很不相同,其他求值器一般都是通過解釋的方式運行,而Aviator則是直接將表達式編譯成Java字節碼
,交給JVM去執行。簡單來說,Aviator的定位是介於Groovy這樣的重量級腳本語言和IKExpression這樣的輕量級表達式引擎之間。
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