思維導圖
簡介
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允許使用關鍵字 null
或 nil
表示一個空值玄括。
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 字面值
布爾字面值由保留關鍵字true
和 false
表示。
6.7 Null 字面值
Null字面值由保留的關鍵字 null
或 nil
表示隅茎。
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允許使用def
或function
關鍵字定義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