mvel2.0語法指南


雖然mvel吸收了大量的java語法,但作為一個表達式語言,還是有着很多重要的不同之處,以達到更高的效率,比如:mvel像正則表達式一樣,有直接支持集合、數組和字符串匹配的操作符。

 除了表達式語言外,mvel還提供了用來配置和構造字符串的模板語言。

mvel2.x表達式包含以下部分的內容:屬性表達式,布爾表達式,方法調用,變量賦值,函數定義。

一、基本語法

MVEL 是一種基於java語法,但又有着顯著不同的表達式語言。與java不同,MVEL是動態類型(帶有可選分類),也就是說在源文件中是沒有類型限制的。一條MVRL表達式,簡單的可以是單個標識符,復雜的則可能是一個充滿了方法調用和內部集合創建的龐大的布爾表達式。

1、簡單的屬性表單式:user.name

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

2、布爾表達式:

MVEl也可以用來表示一個布爾表達式,如:user.name == 'John Doe'.像java一樣,MVEL支持所有優先級規則,包括通過括號來控制執行順序,如:(user.name == 'John Doe') && ((x * 2) - 1) > 20

3、復合語句表達式:

在一段腳本里,你可以寫任意多個語句,但注意要用分號來作為每個語句的結束符,只有一個語句時或最后一個語句時除外。例如:statement1; statement2; statement3 注意最后一個語句沒有分號。另外,換行不能替代分號來作為一個語句的結束標識。

4、返回值:

比如,MVEL使用了輸出最后值原則,也就是說,盡管MVEL定義了return關鍵字,但卻沒有必要用它。例如;

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

這段腳本將最后一個表達式的值a作為自己的值返回,功能上它與下面這段腳本等價:a = 10;
b = (a = a * 2) + 10;
return a;

二、操作符

下面列出了MVEL中所有的操作符:

一元操作符:

new,用來實例化對象,例:new String("foo")

with,對單個對象執行多個操作,例:with (value) { name = 'Foo', age = 18, sex = Sex.FEMALE }

assert,用一個AssertionError 斷言一個值的對錯,例:assert foo != null

isdef,用來判斷一個變量在某個范圍內是否定義,例:isdef variableName

!,布爾取反操作符,例: !true == false

比較運算符;

常見的比較運算符==,!= ,>,<,>=,<=等不再贅述

contains,判斷左邊的值是否包含右邊的值,如: var contains "Foo"

is/instance of ,判斷左邊的值是否是右邊的類的實例,如:var instanceof Integer

strsim,比較兩個字符串的相似度,返回一個百分數,如; "foobie" strsim "foobar"
soundslike,比較兩個字符串的發音,如:"foobar" soundslike "fubar"

 

邏輯運算符:

&&,||略

or,用於多個值間進行邏輯或運算,如:foo or bar or barfoo or 'N/A'
~=,正則表達式匹配符,如:foo ~= '[a-z].+'

 

按位運算符:&,|,^等

數學運算符:+,-,*,/等

其它運算符:

+,字符串連接運算,如:"foo" +"bar"

#,字符連接運算,如:1 # 2返回"12"

in,投影整個項目集合,如:(foo in list)

=,賦值運算符,如:var = "foobar"


三、值判斷

在MVEL中所有的判斷是否相等 都是對值的判斷,而沒有對引用的判斷,因此表達式foo == 'bar' 等價於java中的foo.equals("bar").

1、判斷值是否為emptiness(需要解釋emptiness)

MVEL提供了一個特殊的字符來表示值為emptiness的情況,叫作empty,如:foo == empty,若foo滿足emptiness的任何條件,這個表達式值都為true

 

2、為null測試

MVEL中,null和nil都可以用來表示一個空值,如:foo == null ; foo == nil;

 

3、強制轉換

當兩個不同類型且沒有可比性的值進行比較時,需要將左邊的值強制轉換成右邊的值的類型時,MVEL會應用類型強制轉換系統,反之亦然。如:"123" == 123;這個表達式的值為true,因為為了執行比較,強制類型轉換系統會隱式的將數字123轉換成字符串。


四、列表、map和數組

在MVEL中你可以使用非常簡單的語法來描述列表、map、數組,且看下面的例子:
['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內部數據結構,功能非常強大,你可以在任何地方使用它,甚至可以作為參數使用,如:

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

1、列表

列表用下面的格式來描述:[item1, item2, ...],如:["Jim", "Bob", "Smith"]

2、map

map的描述格式:[key1 : value1, key2: value2, ...],如:["Foo" : "Bar", "Bar" : "Foo"]
3、數組,格式:{item1, item2, ...},如:{"Jim", "Bob", "Smith"}
4、數組的強制轉換

關於數組,需要知道的一個非常重要的方面是,它可以被強制轉換成其它類型的數組,當你聲明一個數組時,是不直接指定其類型的,但你可以通過將其傳遞給一個接收int[]類型參數的方法來指定。如:

foo.someMethod({1,2,3,4});在這種情況下,當MVEL發現目標方法接收的是一個int[],會自動的將{1,2,3,4}轉換成int[]類型。

 

五、屬性訪問

對於bean屬性的訪問,在 Groovy, OGNL, EL等腳本語言的bean 屬性表達式中已經形成了一個相對比較穩定的方式,MVEL也采用了這一方式。和其它語言必須通過底層的方法來控制權限不同的是,MVEL提供了一套獨立的,統一的語法來訪問屬性,靜態字段還有map。

1、bean properties

大多數java開發者都熟悉 getter/setter 模式,並在java對象中用它來封裝屬性的訪問權限。例如,你可能會通過下面的方式訪問一個對象的屬性:

user.getManager().getName();

簡便起見,在MVEL中你也可以用下面的表達式來訪問:

user.manager.name
注意:當一個對象中的字段的作用域是public時,MVEL仍然傾向於通過get方法來訪問其屬性。

2、Null-Safe Bean Navigation

有時,當你的表達式中會含有null元素時,這時就需要你進行一個為空判斷,否則就會發生錯誤。當你使用null-safe操作符時你可以簡化這個操作:user.?manager.name
它相當於:if (user.manager != null) { return user.manager.name; } else { return null; }
3、集合

集合的遍歷也可以通過簡單的語法來實現:

List:可以像訪問數組一樣訪問List,如:user[5],這等價與java代碼中的user.get(5);

Map:Map的訪問和訪問數組也非常相似,不同的是,在訪問Map時索引值可以是任意對象,如:user["foobar"]

這等價於java代碼中的user.get("foobar");當Map的key是String類型時,還可以使用特殊的方式來訪問,如:user.foobar,也就是允許你把map本身看成一個虛擬的對象,來訪問其屬性

4、字符串作數組

為了能使用屬性的索引(迭代也是如此),所有的字符串都可以看成是一個數組,在MVEL中你可以用下面的方式來獲取一個字符串變量的第一個字符:

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

 



六、常量

在腳本語言中,一段文字用來代表一個固定的值 

1、字符串常量:

字符串常量可以用一對單引號或一對雙引號來界定。如:

"This is a string literal"
'This is also string literal'

字符串中的特殊字符:

  • // - 代表一個反斜杠.
  • /n - 換行符
  • /r -回車符
  • /u#### - Unicode 字符(如: /uAE00)
  • /### - Octal字符(如: /73)

2、數字常量 

 整數可以表示為十進制(基數為10),8進制(基數為8),或十六進制(基數為16)。

一個十進制數字,不從零開始(相對於8進制、16進制而言),可以表示任意數,如:125

一個八進制數,以0為前綴,后面跟着0到7內的數字

一個十六進制,以0X為前綴,后面可以跟着0-9,A-F范圍內的數字

3、浮點型常量

如:10.503 // a double
94.92d // a double
14.5f // a float
4、BigInteger 和 BigDecimal型常量

如:104.39484B // BigDecimal
8.4I // BigInteger
5、布爾型常量

布爾型常量用保留關鍵字true和false來表示。

6、空常量

用null或nil來表示

七、程序控制

事實上,MVEL的強大已經超出了簡單的表達式。它提供了一系列的程序控制操作符來提高你的腳本操作,

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!"); } 2、三元聲明
其實就是Java中的條件表達式,如:var > 0 ? "Yes" : "No";
可以嵌套,如:var > 0 ? "Yes" : (var == -1 ? "Minus One!" : "No") 
3、foreach
MVEL的強大特性之一就是其Foreach操作符,在功能和語法上,他都類似於java1.5中的for each操作符,它接收用冒號隔開的兩個參數,
第一個是當前元素的一個域變量,而第二個是要迭代的集合或數組。如下所示:
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進行計數(從1開始):

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

輸出:123456789

注意:像java5.0一樣,在MVEL2.0中,可以將foreach簡化成關鍵字for來使用,如:

for (item : collection) { ... } 4、for循環
MVEL實現了標准的C語言的for循環:
for (int i =0; i < 100; i++) { System.out.println(i); } 5、Do While,Do Until
和java中的意義一樣,MVEL也實現了Do While,Do Until,While和Until意義正好相反。
do { x = something(); } while (x != null); 在語義上相當於:
do { x = something(); } until (x == null); 7、While, Until
MVEL中實現了標准的While,並添加了一個與之相反的Until
while (isTrue()) { doSomething(); } 或者是:
until (isFalse()) {
   doSomething();
}
八、投影和交集
簡而言之,投影(也叫列表解析)是描述集合的方法之一,通過非常簡單的語法,你可以檢索集合中非常復雜的對象模型。假設有一個User對象的
集合,每一個User都有一個Parent。現在你想獲得集合users中的所有parent的name的列表(假設Parent中有字段name),你可以這樣來寫:
parentNames = (parent.name in users);
你甚至可以執行嵌入式操作,設想,User對象有個集合成員叫做familyMembers,現在我們想獲得一個所有家庭成員姓名的集合:
familyMembers = (name in (familyMembers in users));
你可以通過用if運算符構造的條件來過濾投影:(doSomeMethod() in listOfThings if $.shouldBeRun())
其中的$是一個占位符,用來表示被過濾的元素。其實,它就是一個存在於投影上下文內部的普通變量,你還可以用它返回一個列表的投影中的當前
元素:
($ in fooList if $.name contains 'foobie')
其它例子:
(toUpperCase() in ["foo", "bar"]); // returns ["FOO", "BAR"] (($ < 10) in [2,4,8,16,32]); // returns [true, true, true, false, false] ($ in [2,4,8,16,32] if $ < 10); // returns [2,4,8] 下面是一個通過投影實現的快速排序算法:
/**
 * Sample MVEL 2.0 Script
 * "Functional QuickSort" * by: Christopher Michael Brock, Inspired by: Dhanji Prasanna */ import java.util.*; // the main quicksort algorithm def quicksort(list) { if (list.size() <= 1) { list; } else { pivot = list[0]; concat(quicksort(($ in list if $ < pivot)), pivot, quicksort(($ in list if $ > pivot))); } } // define method to concatenate lists. def concat(list1, pivot, list2) { concatList = new ArrayList(list1); concatList.add(pivot); concatList.addAll(list2); concatList; } // create a list to sort list = [5,2,4,1,18,10,15,1,0]; // sort it! quicksort(list); 
九、賦值
MVEL允許你對表達式中的變量進行賦值,以便在運行時獲取,或在表達式內部使用。因為MVEL是動態類型語言,所以你不必為了聲明一個變量
而指定其類型。當然,你也可以選擇指定。
str = "My String"; // valid String str = "My String"; // valid 
與java語言不同的是,當給一個指定類型的變量賦值時,MVEL會提供自動的類型轉換(可行的話),如:
String num = 1; assert num instanceof String && num == "1"; 對於動態類型變量而言,你要想對其進行類型轉換,你只需要將值轉換成相應的類型既可:
num = (String) 1; assert num instanceof String && num == "1"; 十、
MVEL可以使用def或function關鍵字來定義本地函數。
函數必須是先聲明后引用,唯一例外的是遞歸調用的時候。
1、簡單示例
定義函數:
def hello() { System.out.println("Hello!"); } 定義了一個沒有參數的函數hello.當調用該函數時會在控制台打印"Hello!". An MVEL-defined function works just like any regular method call,
 and resolution preference is to MVEL functions over base context methods.
hello(); // calls function
2、傳參和返回值
函數可以接收參數和返回一個值,看下面的例子:
def addTwo(a, b) { 
   a + b;
}
這個函數會接收兩個參數(a和b),然后將這兩個變量相加。因為MVEL遵循last-value-out原則,所以

結果將會被返回。因此,你可以這樣來使用這個函數:

val = addTwo(5, 2);
assert val == 10; 

當然,也可以使用return 關鍵字來強迫從程序內部返回一個函數值。 3、closures

MVEL支持closure,雖然,其功能與本地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 }); 十一、Lambda表達式
MVEL允許定義Lambda方法,如下所示:
threshold = def (x) { x >= 10 ? x : 0 }; 
result = cost + threshold(lowerBound);
上面的例子定義了一個Lambda,並將其賦值給變量"threshold".Lambda實質上就是一個用來給變量賦值的函數,也是closure
十二、宏
MVEL支持通過宏定義來用外部的可擴展資源來代替一個標記,這是它的一項基本的功能。這一功能被用來創建封裝了解釋器的特殊關鍵字。
一個宏可以由任意多個合法的標識符組成,如:modify
考慮下面的代碼:
modify (obj) { value = 'foo' };
這不是一個合法的MVEL表達式,因為畢竟在MVEL中沒有modify這個關鍵字。然而,在JBoss Drools 中會用這一功能通過用字符串
@Modify with替代modify標識符來實現結構變化監聽器,
1、 org.mvel.Macro 接口:
public interface Macro { public String doMacro(); }
這個接口非常簡單,執行時方法 doMacro() 會返回一個字符串來代替原有的標記,例如:
Macro modifyMacro = new Macro() {
     public String doMacro() { return "@Modify with"; } } 
2、使用MacroProcessor
MacroProcessor是一個輕量級的快速文本轉換器,它使用定義的宏來替代所有相對應的標識符。定義的宏存儲在一個Map中,需要匹配的標識符作為
鍵,宏的實例作為值,然后傳遞給MacroProcessor,如:
Map<String, Macro> myMacros = new HashMap<String, Macro>(); // Add modifyMacro to the Map myMacros.put("modify", modifyMacro); // Create the macro processor MacroProcessor macroProcessor = new MacroProcessor(); // Add the macro map to the macro processor macroProcessor.setMacros(myMacros); // Now we pre-parse our expression String parsedExpression = macroProcessor.parse(expression);
返回的字符串就會直接傳遞給MVEL的編譯器。
十三、攔截器
MVEL提供了在編譯后的表達式里使用攔截器的功能,這對實現監聽器或是在表達式內部觸發一個外部事件特別有用。聲明攔截器用的是@Syntax,
有點像java語言中的注解。攔截器的聲明應該放在待封裝的語句之前,它可以實現之前或之后的監聽器,或二者都實現。例如:
@Intercept
foreach (item : fooItems) { 
   total += fooItems.price;
}
在這個特殊的句子里,攔截器封裝了整個的foreach塊,因此,如果攔截器實現了之后的監聽器,則當foreach循環結束后,攔截動作將被觸發。
1、攔截器接口org.mvel.intergration.Interceptor 
public interface Interceptor { public int doBefore(ASTNode node, VariableResolverFactory factory); public int doAfter(Object exitStackValue, ASTNode node, VariableResolverFactory factory); } 攔截器接口提供了兩個待實現的方法:doBefore和doAfter,下面我們來看一下MVEL運行時傳遞給這兩個方法的參數的含義
2、doBefore
在執行封裝的命令前會執行doBefore方法。
org.mvel.ASTNode::node
 ASTNode句柄是 攔截器內部ASTNode 的一個引用,可以用來獲取實際編譯后的代碼的信息。

org.mvel.integration.VariableResolverFactory::factory

變量分析器工廠提供表達式內當前范圍內變量的訪問權限。

3、doAfter

在執行完封裝的指令后執行doAfter方法

java.lang.Object::exitStackValue

doAfter方法雖是在語句執行后執行,但卻不是在幀結束前。因此,操作結束時留在棧中的任何數據都仍然存在,而且能被攔截器訪問。例如:

@Intercept cost += value; 這是一個比較特殊的句子,cost的原值一直保存在棧中,直到整個幀執行完畢,因此,這個值在調用doAfter方法時可以通過exitStackValue訪問到。

org.mvel.ASTNode::node

這是傳遞到doBefore方法中的同一個AST 元素,更多細節參考doBefore方法。

org.mvel.intergration.VariableResolverFactory::factory

同doBefore方法

4、編譯器中使用攔截器

為了能是攔截器連到表達式中,必須在編譯表達式之前提供攔截器,因此有一點需要注意,攔截器可能不用於MVEL解釋器。

攔截器是儲存在map里提供給編譯器的,map中的鍵為攔截器的名稱,值為攔截器實例。如:

// Create a new ParserContext ParserContext context = new ParserContext(); Map<String, Interceptor> myInterceptors = new HashMap<String, Interceptor>(); // Create a simple interceptor. Interceptor myInterceptor = new Interceptor() { public int doBefore(ASTNode node, VariableResolverFactory factory) { System.out.println("BEFORE!"); } public int doAfter((Object value, ASTNode node, VariableResolverFactory factory) { System.out.println("AFTER!"); } }; // Now add the interceptor to the map. myInterceptors.put("Foo", myInterceptor); // Add the interceptors map to the parser context. context.setInterceptors(myInterceptors); // Compile the expression. Serializable compiledExpression = MVEL.compileExpression(expression, context);

十四、數據類型
MVEL是一種有靜態類型的動態類型語言。大部分MVEL使用者都比較傾向於用動態類型,因為它非常簡單易用。如:
a = 10; // declare a variable 'a' b = 15; // declare a variable 'b';  a + b;
1、動態類型與強制轉換
像MVEL這種直接與java對象(靜態類型)打交道的語言,最重要的一個方面就是強制類型轉換。因為MVEL不能對一個java.lang.String對象和一個
java.lang.Integer對象進行數學運算,所以就必須把其中一個的類型轉換成另一個的類型。
2、性能考慮
在你的應用中集成一個像MVEL這樣的東西,性能考慮是必須的。對於重量級程序加載,強制類型轉換超負荷等可以通過緩存和優化器(僅用於預編譯
的表達式)來解決。然而,並不是所有的強制類型轉換都可以忽略不管,關鍵要看它是在做什么。
比如,當一個String類型的變量在運行中要看成一個整形變量時,要阻止運行時將字符串轉換成整型簡直是不可能的,像這種情況,一定要考慮其性能。
3、方法調用
調用方法是強制轉換的最重要的一個方面。從根本上講,你可以直接調用,而無需關心參數是什么。解釋器或編譯器會分析方法的參數類型,然后確定
要進行哪一種強制轉換,如果是重載的方法,它會選擇與輸入類型最接近的那個方法進行調用,以盡可能的避免強制轉換。 
4、數組
數組是強制類型轉換中最有趣的一個方面,因為MVEL缺省使用無類型數組(也就是說任何情況下都是Object[]),只有當遇到類型沖突時,才會嘗試將
整個數組轉換成所需的類型,比如在方法調用傳參時。
示例:
myArray = {1,2,3};

// pass to method that accepts String[] myObject.someMethod(myArray);
在這個例子里,somMethod方法接收字符數組,這在MVEL中不會出錯,相反,MVEL會將myArray轉換成字符數組。
5、靜態類型
靜態類型與java類似,只不過默認情況下仍然會進行強制轉換。
int num = 10;
這個句子聲明了一個整型變量num,這時,MVEL運行時會強制轉換類型。比如,聲明后賦值一個不合適類型的數據,結果就會出現異常。
num = new HashMap(); // will throw an incompatible typing exception.
但如果是一個可以進行強制類型轉換的值時,MVEL就會進行強制轉換。
num = "100"; // will work -- parses String to an integer. 6、嚴格類型
嚴格類型是編譯器的一種可選模式,在這種模式下,所有的類型都必須限定,不管是聲明時還是在引用時。
啟動嚴格模式:
當編譯一個表達式時,可以通過ParserContext設置setStrictTypeEnforcement(true)將編譯器設置成嚴格模式。
嚴格類型通過表達式內的具體類型聲明或提前告訴轉換器確定的類型來完成。例如:
ExpressionCompiler compiler = new ExpressionCompiler(expr); ParserContext context = new ParserContext(); context.setStrictTypeEnforcement(true); context.addInput("message", Message.class); context.addInput("person", Person.class); compiler.compile(context);

在這個例子中我們通知編譯器表達式將接收兩個外部輸入:message 和 person 及它們的類型。這就使得編譯器可以在編譯時確定某一

個調用是否是安全的,從而防止了運行時的錯誤。

7、強類型

強類型是MVEL2.0新引入的概念。強類型模式要求所有的變量必須是限定的類型,從這一點上它與嚴格類型不同。差別在於嚴格模式只是在編譯時

限定屬性和方法調用的類型。

十五、Shell
通過交互式的Shell,你可以直接與MVEL打交道,去探究MVEL的特性。
1、運行Shell
只需運行MVEL的分布式jar包既可運行Shell:java -jar mvel2-2.0.jar 或者,你也可以在你喜歡的IDE中通過配置一個該類的運行環境來運行。
十六、FAQ
1、為什么不能使用.class的引用?
MVEL沒有像java中的用來執行類型文件的.class標識符,其實它本身就沒有class文件,而只需要通過其名稱就可以引用這個類。比如,一個方法
接收一個Class類型作為參數,你可以這樣來調用:
// MVEL
someMethod(String); // Java-equivalent someMethod(String.class);

事實上,MVEL將.class視作一個普通的bean屬性,因此,如果使用String。class,那返回值就會是指向java.lang.Class本身的一個

java.lang.Class 的實例,因此就相當於在java中使用String.class.getClass() .

原理是這樣的,MVEL使用動態類型系統,這樣類型就被當作普通的變量來看待,而不是像java中限定類文件。所以,MVEL允許class類型作為

一個普通變量來引用,而不像java。

十七、為什么不能用object.class.name的格式?
這是MVEL的一個限制,可能會在將來的某個版本中標記出來,但bean屬性不支持對Class的引用。並不是說不能調用Class的方法,你必須使用
限定的方法,像:
someVar.class.getName();     // Yes!
someVar.class.name;          // No! someVar.getClass().getName() // Yes! someVar.getClass().name // No! 
這一規定完全限制了java.lang.Class僅可用做某個變量的屬性,並限制了MVEL處理類的引用的方式。


免責聲明!

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



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