Aviator——輕量級Java表達式求值引擎


簡介

Aviator是一個高性能、輕量級的java語言實現的表達式求值引擎,主要用於各種表達式的動態求值。現在已經有很多開源可用的java表達式求值引擎,為什么還需要Avaitor呢?

Aviator的設計目標是輕量級高性能 ,相比於Groovy、JRuby的笨重,Aviator非常小,加上依賴包也才450K,不算依賴包的話只有70K;當然,Aviator的語法是受限的,它不是一門完整的語言,而只是語言的一小部分集合。

其次,Aviator的實現思路與其他輕量級的求值器很不相同,其他求值器一般都是通過解釋的方式運行,而Aviator則是直接將表達式編譯成Java字節碼,交給JVM去執行。簡單來說,Aviator的定位是介於Groovy這樣的重量級腳本語言和IKExpression這樣的輕量級表達式引擎之間。
screenshot.png


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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM