MVEL2.0表達式使用指南

思維導圖

MVEL2.0表達式使用指南

簡介

MVEL為 MVFLEX Expression Language(MVFLEX表達式語言)的縮寫,是一種基于Java語法减牺,但又有著顯著不同的表達式語言。與Java不同的是,MVEL是一種動態(tài)/靜態(tài)可嵌入的表達式語言既荚,意味著源代碼中不需要類型限定稚失。
簡單來說是一種強大的表達式解析器恰聘。我們可以自己寫一些表達式,交給mvel進行解析計算晴叨,得到這個表達式計算的值凿宾。

太抽象?先來個demo吧初厚。孙技。。亚情。

package com.learn.mvel;

import org.mvel2.MVEL;

/**
 * @Auther: fc.w
 * @Date: 2021/1/16 23:29
 * @Description:
 */
public class MvelDemo {

    public static void main(String[] args) {
        // java 語法
        int result = 1 + 2;
        System.out.println(result); // 結果:3

        // mvel 語法
        Object res = MVEL.eval("1 + 2");
        System.out.println(res); // 結果:3

    }

}

是不是很吃鯨哈雏∩驯瘢“1 + 2” 就是一個表達式,第一種是Java硬編碼實現(xiàn)的計算結果栅迄,但第二種直接給evel函數(shù)傳遞一個表達式字符串皆怕,直接能計算出結果。

哈哈哈岂津,接下來一起了解一下語法吧悦即,至于使用場景,下篇文章介紹MVEL在我們公司的個性化推薦平臺中做了什么粱甫。

1. 基本語法

1.1 簡單屬性表達式

user.name

在這個表達式中茶宵,我們只是有一個標識符(user.name)宗挥,這就是我們所說的MVEL的AA級屬性表達式,該表達式的唯一目的是獲取一個變量或上下文對象的屬性契耿。屬性表達式是MVEL的最常見的用途之一搪桂,通過它,MVEL可以用來作為一個高性能拙泽,易使用的反射優(yōu)化器顾瞻。

1.2 布爾表達式

MVEL甚至可以用于執(zhí)行布爾表達式:

user.name == 'John Doe'

像java一樣德绿,MVEL支持所有優(yōu)先級規(guī)則移稳,包括通過括號來控制執(zhí)行順序,如:

(user.name == 'John Doe') && ((x * 2) - 1) > 20

1.3 復合語句表達式

可以編寫具有任意數(shù)量語句的腳本古毛,使用分號來表示一個語句的終止稻薇。只有一個語句或在腳本中的最后一個語句的情況下,可以不用使用分號:statement1; statement2; statement3
最后的語句可以不使用分號桨仿,因為是腳本中的最后一個語句案狠。注意骂铁,換行不能替代分號來作為一個語句的結束標識。

1.4 返回值

MVEL表達式使用最后值輸出原則(a last value out principle)瘪校。這意味著雖然MVEL支持return關鍵字,但可以不使用它伸辟。 例如:

a = 10;
b = (a = a * 2) + 10;
a;

在上面的例子中馍刮,表達式返回了a的值卡啰,因為其是表達式的最后一個值。 它在功能上與以下相同:

a = 10;
b = (a = a * 2) + 10;
return a;

2. 操作符

2.1 一元操作符

  • new:用來實例化對象振湾,例:new String("foo")
  • with:對單個對象執(zhí)行多個操作押搪,例:with (value) { name = 'Foo', age = 18, sex = Sex.FEMALE }
  • assert:用一個AssertionError 斷言一個值的對錯浅碾,例:assert foo != null
  • isdef:用來判斷一個變量在某個范圍內是否定義垂谢,例:isdef variableName
  • @闹臁:布爾取反操作符力试,例: 懂版!true == false

2.2 比較運算符

  • 常見的比較運算符==躏率,薇芝!= ,>,<,>=,<=等不再贅述
  • contains:判斷左邊的值是否包含右邊的值嚷缭,如: var contains "Foo"
  • is/instance of :判斷左邊的值是否是右邊的類的實例阅爽,如:var instanceof Integer
  • strsim:比較兩個字符串的相似度荐开,返回一個百分數(shù)晃听,如:"foobie" strsim "foobar"
  • soundslike:比較兩個字符串的發(fā)音能扒,如:"foobar" soundslike "fubar"

2.3 邏輯運算符

  • &&、||: 略
  • or:用于多個值間進行邏輯或運算辛润,如:foo or bar or barfoo or 'N/A'
  • ~=:正則表達式匹配符频蛔,如:foo ~= '[a-z].+'

2.4 按位運算符

&秦叛,|,^等

2.5 數(shù)學運算符

+挣跋,-,*舟肉,/等

2.6 其它運算符:

  • +路媚,字符串連接運算整慎,如:"foo" + "bar"
  • #,字符連接運算撤师,如:1 # 2 返回 "12"
  • in剃盾,投影整個項目集合淤袜,如:(foo in list)
  • =铡羡,賦值運算符,如:var = "foobar"

3. Value 校驗

在 MVEL 中所有的等式判斷都是基于值,并非是對象的引用论矾,因此表達式 foo == 'bar' 等價于java中的 foo.equals("bar")贪壳。

3.1 Empty

MVEL 提供了一個特殊的字面值闰靴,用于校驗一個值是否為""或者null钻注,命名為empty幅恋。
例如:a == empty
如果 a 的值滿足 empty 的要求,則示例表達式將為 true淑翼。

package com.learn.mvel;

import org.mvel2.MVEL;

import java.util.HashMap;
import java.util.Map;

/**
 * @Auther: fc.w
 * @Date: 2021/1/17 00:17
 * @Description:
 */
public class MvelEmpty {

    public static void main(String[] args) {
        String expression = "a == empty && b == empty";

        Map<String, Object> paramMap = new HashMap<>();
        paramMap.put("a", "");
        paramMap.put("b", null);

        Object object = MVEL.eval(expression, paramMap);
        System.out.println(object); // true
    }

}

3.2 Null

MVEL允許使用關鍵字 nullnil 表示一個空值玄括。

package com.learn.mvel;

import org.mvel2.MVEL;

import java.util.HashMap;
import java.util.Map;

/**
 * @Auther: fc.w
 * @Date: 2021/1/17 00:22
 * @Description:
 */
public class MvelNull {

    public static void main(String[] args) {
        String expression = "a == null && b == nil";

        Map<String, Object> map = new HashMap<>();
        map.put("a", null);
        map.put("b", null);

        Object obj = MVEL.eval(expression, map);
        System.out.println(obj); // true
    }

}

3.3 強制類型轉換

MVEL的強制類型轉換系統(tǒng)適用于如下場景:通過試圖將右邊的值強制轉換為左邊值的類型來比較兩個無法比較的類型胃惜,反之亦然洁墙。

"123" == 123;

上述表達式在MVEL中返回 true热监,因為強制類型轉換系統(tǒng)將強制將無類型數(shù)字123轉換為字符串來執(zhí)行比較孝扛。

    public static void main(String[] args) {
        String expression = "a == b";

        Map<String, Object> map = new HashMap<>();
        map.put("a", "12");
        map.put("b", "12");

        Object obj = MVEL.eval(expression, map);
        System.out.println(obj); // true
    }

4. 集合List, Maps, 數(shù)組

在MVEL中可以使用非常簡單的語法來描述List苦始、map、數(shù)組理郑。
示例:

["Bob" : new Person("Bob"), "Michael" : new Person("Michael")]

功能上等同于以下代碼:

Map map = new HashMap();
map.put("Bob", new Person("Bob"));
map.put("Michael", new Person("Michael"));

這是在MVEL中表達數(shù)據(jù)結構的非常強大的方法您炉∽簦可以在任何地方使用這些結構法瑟,甚至作為方法的參數(shù):

something.someMethod(["foo" : "bar"]);

4.1 Lists

可以使用下列格式表示

[item1霎挟,item2氓扛,...]

Example:

["Jim", "Bob", "Smith"]

4.2 Maps

Maps可以使用下列格式表示:

[key1 : value1, key2: value2, ...]

Example:

["Foo" : "Bar", "Bar" : "Foo"]

4.3 數(shù)組

Arrays可以使用下列格式表示:

{item1, item2, ...}

Example:

{"Jim", "Bob", "Smith"}

4.4 Array 強制類型轉換

關于數(shù)組,需要知道的一個非常重要的方面是狂魔,它可以被強制轉換成其它類型的數(shù)組淫痰,當你聲明一個數(shù)組時待错,是不直接指定其類型的火俄,但你可以通過將其傳遞給一個接收int[]類型參數(shù)的方法來指定。如:foo.someMethod({1,2,3,4})
在這種情況下适瓦,MVEL會看到目標方法接受一個int[]參數(shù)并自動轉換數(shù)組類型玻熙。

5. 屬性

MVEL屬性遵循在其他語言(如Groovy嗦随,OGNL敬尺,EL等)中的bean屬性表達中的完整約定砂吞。與需要限定的其他語言不同呜舒,MVEL提供了訪問屬性笨奠,靜態(tài)字段般婆,Map等的單一統(tǒng)一語法蔚袍。

5.1 Bean Properties

大多數(shù)Java開發(fā)人員熟悉并使用其Java對象中的getter/setter方法,以便封裝屬性訪問渠脉。 例如瓶佳,你可以從對象訪問屬性:

user.getManager().getName();

為了簡化此操作霸饲,你可以使用以下表達式訪問相同的屬性:

user.manager.name

Example:

package com.learn.mvel;

import org.mvel2.MVEL;

import java.util.HashMap;
import java.util.Map;

/**
 * @Auther: fc.w
 * @Date: 2021/1/17 00:48
 * @Description:
 */
public class MvelParam {

    public static void main(String[] args) {
        Fruit fruit = new Fruit();
        fruit.setName("Apple");

        // String expression = "fruit.getName()";
        String expression = "fruit.name";

        Map<String, Object> paramMap = new HashMap<>();
        paramMap.put("fruit", fruit);
        
        Object object = MVEL.eval(expression, paramMap);
        System.out.println(object); // Apple
    }

}

注意:當一個對象中的字段的作用域是public時厚脉,MVEL仍然希望于通過get方法來訪問其屬性傻工。

5.2 Null-Safe Bean Navigation

當表達式中會含有null元素時精钮,這時就需要進行一個為空判斷轨香,否則就會發(fā)生錯誤。當使用null-safe操作符時科雳,可以簡化這個操作:

user.?manager.name

功能上等同于:

if (user.manager != null) { return user.manager.name; } else { return null; }

5.3 Collections

集合的遍歷也可以使用縮寫語法實現(xiàn)糟秘。

5.3.1 List

List的訪問與數(shù)組相同尿赚。 例如:

user[5]

相當于Java代碼:

user.get(5);
5.3.2 Map

Map以數(shù)組相同的方式訪問凌净,除非任意對象可以作為索引值傳遞冰寻。 例如:

user["foobar"]

相當于Java代碼:

user.get("foobar");

對于使用字符串作為key的Map斩芭,你可以使用另一種特殊語法:

user.foobar

...允許你將Map本身視為虛擬對象。

5.4 Strings as Arrays

為了使用屬性索引(以及迭代)贬养,所有字符串都被視為數(shù)組煤蚌。 在MVEL中尉桩,你可以訪問String變量中的第一個字符:

foo = "My String";
foo[0]; // returns 'M';

6. 常量

常量用于表示特定腳本源中的固定值

6.1 字符串常量

字符串常量可以用單引號或雙引號表示蜘犁。
"This is a string literal"
'This is also string literal'

6.2 字符串轉義序列

  • \\ 雙重轉義允許在字符串中出現(xiàn)單個反斜杠这橙。
  • \n 新行
  • \r 回車
  • \u#### Unicode字符(示例:\ uAE00)
  • \### 八進制字符(示例:\ 73)

6.3 數(shù)值型字面值

整數(shù)可以十進制(10位)屈扎,八進制(8位)或十六進制(16位)表示撩匕。

  • 十進制整數(shù)可以表示為不以零開始的任何數(shù)字止毕。
    125 // 十進制

  • 八進制表示為帶有0前綴的數(shù)字扁凛,后跟數(shù)字范圍從0到7谨朝。
    0353 // 八進制

  • 十六進制表示為帶有0x前綴的數(shù)字字币,后跟數(shù)字范圍為0-9..A-F纬朝。
    0xAFF0 // 十六進制

6.4 浮點型字面值

浮點數(shù)由整數(shù)部分和由點/周期字符表示的小數(shù)部分組成骄呼,并具有可選的類型后綴共苛。

10.503 // a double
94.92d // a double
14.5f // a float

6.5 BigInteger和BigDecimal字面值

你可以使用后綴B和I來表示BigInteger和BigDecimal字面值(大寫字母是必填字段)判没。

104.39484B // BigDecimal
8.4I // BigInteger

6.6 Boolean 字面值

布爾字面值由保留關鍵字truefalse 表示。

6.7 Null 字面值

Null字面值由保留的關鍵字 nullnil 表示隅茎。

7. 類型常量

類型文字與Java中的類似澄峰,具有以下格式:

<PackageName>.<ClassName>

所以一個類可能是被限定為如下:

java.util.HashMap

或者如果類是通過內聯(lián)或外部配置引入的,那么可以使用其非限定名稱引用它:HashMap

7.1 嵌套類

MVEL 2.0中的標準點符號.(如Java中)無法訪問嵌套類辟犀。 相反俏竞,你必須使用符號限定這些類堂竟。

org.proctor.Person$BodyPart

8. 程序判斷/遍歷

8.1 If-Then-Else

MVEL提供了完整的C/Java式的if-then-else塊魂毁,如:

if (var > 0) {
   System.out.println("Greater than zero!");
}
else if (var == -1) { 
   System.out.println("Minus one!");
}
else { 
   System.out.println("Something else!");
}

Example:

    public static void main(String[] args) {
        String expression = "if (param > 0) {return \"Greater than zero!\"; } " +
                "else if (param == -1) { return \"Minus one!\"; } " +
                "else { return \"Something else!\"; }";

        Map<String, Object> paramMap = new HashMap<>();
        paramMap.put("param", 2);

        Object object = MVEL.eval(expression, paramMap);
        System.out.println(object); // Greater than zero!
    }

8.2 三元聲明

就像Java一樣,支持三元聲明語句

num > 0 ? "Yes" : "No";

可嵌套三元語句:

num > 0 ? "Yes" : (num == -1 ? "Minus One!" : "No")

Example:

    public static void main(String[] args) {
        String expression = "num > 0  ? \"Yes\" : \"No\";";

        Map<String, Object> paramMap = new HashMap();
        paramMap.put("num", new Integer(1));

        Object object = MVEL.eval(expression, paramMap);
        System.out.println(object); // Yes
    }

8.3 Foreach

MVEL中最強大的功能之一就是foreach操作。 它與Java 1.5中的foreach運算符的語法和功能類似出嘹。它接受由冒號分隔的兩個參數(shù)席楚,第一個是當前元素的局部變量,第二個是要迭代的集合或數(shù)組税稼。

count = 0;
foreach (name : people) {
   count++;
   System.out.println("Person #" + count + ":" + name);
}
     
System.out.println("Total people: " + count);

由于MVEL將字符串視為可迭代對象烦秩,你可以使用foreach塊來迭代字符串(逐字符):

str = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
 
foreach (el : str) {
   System.out.print("["+ el + "]");
}

上面輸出為:

[A][B][C][D][E][F][G][H][I][J][K][L][M][N][O][P][Q][R][S][T][U][V][W][X][Y][Z]

你還可以使用MVEL計數(shù)到一個整數(shù)值(從1):

foreach (x : 9) {
   System.out.print(x);
}

上面輸出為:

123456789

語法注意
從MVEL 2.0開始,可以通過使用for關鍵字簡單地簡化foreach郎仆,就像在Java 5.0中一樣只祠。 例如:

for (item : collection) { ... }

8.4 For 循環(huán)

MVEL 2.0實現(xiàn)標準C for循環(huán):

for (int i =0; i < 100; i++) {
   System.out.println(i);
}

8.5 Do While, Do Until

在MVEL中實現(xiàn)了do while和do until,遵循與Java相同的約定扰肌,帶有until的與while相反抛寝。

do {
   x = something();
} while (x != null);

...在語義上相當于...

do {
  x = something();
} until (x == null);

8.6 While, Until

MVEL 2.0實現(xiàn)標準的while,以及相反的until狡耻。

while (isTrue()) {
   doSomething();
}

或者

until (isFalse()) {
  doSomething();
}

9. 投影與折疊

簡單地說墩剖,投影是表示集合的一種方式∫恼可以使用非常簡單的語法岭皂,檢查集合中非常復雜的對象模型。

想像你有一個User對象的集合沼头。 這些對象中的每一個都有一個Parent爷绘。 現(xiàn)在,你想要在用戶層次結構中獲取父目錄的所有名稱(假設Parent類有一個name字段)进倍,你將會寫下如下內容:
parentNames = (parent.name in users);
甚至可以執(zhí)行嵌套操作土至。想象一下,User對象有一個名為familyMembers的成員集合猾昆,我們想要一個所有家庭成員名稱的列表:
familyMembers = (name in (familyMembers in users));

10. 變量賦值

MVEL允許你可以在表達式中賦值變量陶因,運行時可以提取使用,或在表達式中直接使用垂蜗。
由于MVEL是一種動態(tài)類型的語言楷扬,你不需要指定一個類型來聲明一個新的變量解幽。 但是,你可以選擇這樣做烘苹。

str = "My String"; // valid
String str = "My String"; // valid
然而躲株,與Java不同,MVEL在為類型變量賦值時提供了自動類型轉換(如果可能的話)镣衡。 例如:
String num = 1;
assert num instanceof String && num == "1";

對于動態(tài)類型變量霜定,如果你只想執(zhí)行類型轉換,你可以簡單地將該值轉換為所需的類型:
num = (String) 1;
assert num instanceof String && num == "1";

11. 方法定義

MVEL允許使用deffunction關鍵字定義native函數(shù)廊鸥。
函數(shù)按聲明的順序定義望浩,不能前言引用。 唯一的例外是在函數(shù)本身中黍图,可以直接引用另一個函數(shù)曾雕。

11.1 簡單示例

定義一個簡單的函數(shù):

def hello() { System.out.println("Hello!"); }

這定義了一個名為“hello”的簡單函數(shù),它不接受任何參數(shù)助被。調用該函數(shù)時打印Hello剖张!到控制臺。 MVEL定義的函數(shù)像任何常規(guī)方法調用一樣工作揩环。

    public static void main(String[] args) {
        String expression = "def hello() { return \"Hello!\"; } hello();";

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

        Object object = MVEL.eval(expression, paramMap);
        System.out.println(object); // Hello!
    }

11.2 接受參數(shù)并返回值

函數(shù)可以被聲明為接受參數(shù)搔弄,并且可以返回單個值。如下示例:

def addTwo(a, b) {
   a + b;
}

該函數(shù)將接受兩個參數(shù)(a和b)丰滑,然后將兩個變量相加顾犹。 由于MVEL使用最終值退出原則,所以返回最終結果值褒墨。因此炫刷,你可以使用以下功能:
val = addTwo(5, 2);
assert val == 10;
return 關鍵字也可用于強制從函數(shù)的內部程序流程中返回值。

Example:

    public static void main(String[] args) {
        String expression = "def addTwo(num1, num2) { num1 + num2; } val = addTwo(a, b);";

        Map<String, Object> paramMap = new HashMap();
        paramMap.put("a", 2);
        paramMap.put("b", 4);

        Object object = MVEL.eval(expression, paramMap);
        System.out.println(object); // 6
    }

11.3 Closures

MVEL允許Closures郁妈。 但是浑玛,該功能不能與本地Java方法互操作。

// define a function that accepts a parameter   
def someFunction(f_ptr) { f_ptr(); }
 
// define a var
var a = 10;
 
// pass the function a closure
someFunction(def { a * 10 });

參考資料:
https://www.itdaan.com/blog/2011/06/14/15c2d18f615914b8cc3dd66374e8f5b1.html

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末噩咪,一起剝皮案震驚了整個濱河市顾彰,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌胃碾,老刑警劉巖涨享,帶你破解...
    沈念sama閱讀 222,252評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異仆百,居然都是意外死亡厕隧,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,886評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來吁讨,“玉大人帖族,你說我怎么就攤上這事〉簿簦” “怎么了?”我有些...
    開封第一講書人閱讀 168,814評論 0 361
  • 文/不壞的土叔 我叫張陵甚垦,是天一觀的道長茶鹃。 經常有香客問我,道長艰亮,這世上最難降的妖魔是什么闭翩? 我笑而不...
    開封第一講書人閱讀 59,869評論 1 299
  • 正文 為了忘掉前任,我火速辦了婚禮迄埃,結果婚禮上疗韵,老公的妹妹穿的比我還像新娘。我一直安慰自己侄非,他們只是感情好蕉汪,可當我...
    茶點故事閱讀 68,888評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著逞怨,像睡著了一般者疤。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上叠赦,一...
    開封第一講書人閱讀 52,475評論 1 312
  • 那天驹马,我揣著相機與錄音,去河邊找鬼除秀。 笑死糯累,一個胖子當著我的面吹牛,可吹牛的內容都是我干的册踩。 我是一名探鬼主播泳姐,決...
    沈念sama閱讀 41,010評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼棍好!你這毒婦竟也來了仗岸?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,924評論 0 277
  • 序言:老撾萬榮一對情侶失蹤借笙,失蹤者是張志新(化名)和其女友劉穎扒怖,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體业稼,經...
    沈念sama閱讀 46,469評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡盗痒,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,552評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片俯邓。...
    茶點故事閱讀 40,680評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡骡楼,死狀恐怖,靈堂內的尸體忽然破棺而出稽鞭,到底是詐尸還是另有隱情鸟整,我是刑警寧澤,帶...
    沈念sama閱讀 36,362評論 5 351
  • 正文 年R本政府宣布朦蕴,位于F島的核電站篮条,受9級特大地震影響,放射性物質發(fā)生泄漏吩抓。R本人自食惡果不足惜涉茧,卻給世界環(huán)境...
    茶點故事閱讀 42,037評論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望疹娶。 院中可真熱鬧伴栓,春花似錦、人聲如沸雨饺。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,519評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽沛膳。三九已至扔枫,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間锹安,已是汗流浹背短荐。 一陣腳步聲響...
    開封第一講書人閱讀 33,621評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留叹哭,地道東北人忍宋。 一個月前我還...
    沈念sama閱讀 49,099評論 3 378
  • 正文 我出身青樓,卻偏偏與公主長得像风罩,于是被迫代替她去往敵國和親糠排。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,691評論 2 361

推薦閱讀更多精彩內容