mvel语法指南
mvel受到了java语法的启发,但是存在一些根本性的差异,mvel旨在使其成为更有效的表达式语言.比如直接支持集合、数组和字符串匹配,正则表达式的运算操作等.
mvel2.x有以下几个部分组成:
- Property expressions
- Boolean expressions
- Method invocations
- Variable assignments
- Function definitions
一、基本语法:
1:简单的属性表达式:
user.name
user.name == 'John Doe'
和java一样,mvel支持运算符优先级规则的完整的策略,包括使用括号的能力来控制执行顺序。
(user.name == 'John Doe') && ((x * 2) - 1) > 20
2:复合表达式
可以用任意数量的语句,用分号表示终止声明。
statement1; statement2; statement3
3:返回值:
mvel使用最后一个值作为返回值,开发人员不必显示的指定返回值,
默认为最后一个值最为返回
。
a = 10; b = (a = a * 2) + 10; a;
二、mvel2.0的操作运算
下面给出的是mvel的现有的所有操作运算符合运算逻辑
Unary Operators
Operator | Description | Example |
---|---|---|
new | Object instantiation | new String("foo") |
with | Block WITH Operator. Perform multiple operations on a single object instance | with (value) { name = 'Foo', age = 18, sex = Sex.FEMALE } |
assert | Assert that a value is true or fail with an AssertionError | assert foo != null |
isdef | Tests whether or not a variable is defined within the scope | isdef variableName |
! | Boolean negation operator | !true == false |
Comparison Operators
Operator | Description | Example |
---|---|---|
== | Equality Check. Checks to see if the values on both sides of the operator are equal. Unlike Java, this is not an identity check. | "foo" == "foo" is true |
!= | Not Equals Check. Checks to see if the values on both sides of the operator are not equal. | "foo" == "bar" is false |
> | Greater Than Check. Checks to see if the value on the left side of the operator is greater than than value on the right. | 2 > 1 is true |
< | Less Than Check. Checks to see if the value on the left side of the operator is less than value on the right. | 1 < 2 is true |
>= | Greater Than or Equal. Checks to see if the value on the left hand side is greater than or equal to the value on the right. | 1 >= 1 is true |
<= | Less Than or Equal. Checks to see if the value on the left hand side is less than or equal to the value on the right. | 2 <= 2 is true |
contains | Value Containment Check. Checks to see if the value on the left contains the value on the right. For more details on how this operator works, see Contains Operator | var contains "Foo" |
is orinstanceof | Type Checking Operator. Checks to see if the value on the left is a member of the class on the right. | var instanceof Integer |
strsim | String Similarity Check. Compares to strings and returns a similarity between them as a percentage. See: String Similarity Check. | "foobie" strsim "foobar" |
soundslike | Soundex Check. Performs a soundex comparison between two strings. See: Soundex. | "foobar" soundslike "fubar" |
Logical Operators
Operator | Description | Example |
---|---|---|
&& | Logical AND. Checks to see that the values on both sides of the operator are true. | foo && bar |
|| | Logical OR. Checks to see if either the value on the left or the right is true. | foo || bar |
or | Chained OR. Checks a sequence of values for emptinessand returns the first non-empty value. (This operator, or at least it's equivalent functionality, is referred to as the "elvis operator" in other languages) | foo or bar or barfoo or 'N/A' |
~= | Regular Expression Match. Checks to see if the value on the left matches the regular expression on the right. | foo ~= '[a-z].+' |
Bitwise Operators
Operator | Description | Example |
---|---|---|
& | Bitwise AND. | foo & 5 |
| | Bitwise OR. | foo | 5 |
^ | Bitwise XOR. | foo ^ 5 |
Arithmetic Operators
Operator | Description | Example |
---|---|---|
+ | Addition. Adds the value on the left to the value on the right | 1 + 2 |
- | Subtraction. Subtracts the value on the right from the value on the left | 2 - 1 |
/ | Division. Divides the number on the left by the number on the right | 2 / 1 |
* | Multiplication. Multiples the number on the left by the number on the right | 1 * 2 |
% | Modulus. Divides the number on the left by the number on the right and returns the remainder. | 2 % 1 |
Other Operators
Operator | Description | Example |
---|---|---|
+ | String Concatenation. Overloaded operator for concatenating two strings together. | "foo" + ;bar" |
# | Concatenation Operator. Concatenates two literals as strings. | 1 # 2returns "12" |
in | Projection/Fold. Projects across a collection. See:Projections and Folds. | (foo in list) |
= | Assignment. Assigns the value on the right to the variable on the left. | var = "foobar" |
三、值测试
在mvel中所有的比较都是值比较而不是比较句柄,所有foo=='bar'和java中的equals是相同的
1:判断值是否为emptiness
MVEL提供了一个特殊的字符来表示值为emptiness的情况,叫作empty,如:foo == empty,若foo满足emptiness的任何条件,这个表达式值都为true
For example:
foo == empty
若foo满足emptiness的任何条件,这个表达式值都为true
2:测试null
mvel中可以使用null或者nil来进行空值判断
foo == null; foo == nil; // same as null
3:强制转换
右边的值会强制转换为左边的值在比较的时候:
"123" == 123;
结果为true,因为在比较的时候123被强制转换成string在进行值比较
四、内置集合
mvel可以通过简单的语法创建集合
['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']);
Lists
列表用下面的格式来描述: [item1, item2, ...]
For example:
["Jim", "Bob", "Smith"]
Maps
Map用下面的格式来描述: [key1 : value1, key2: value2, ...]
For example:
["Foo" : "Bar", "Bar" : "Foo"]
Arrays
数组用下面的格式来描述: {item1, item2, ...}
For example:
{"Jim", "Bob", "Smith"}
数组的强制转换
你可以进行如下操作:
foo.someMethod({1,2,3,4});
正如你看到的,虽然你没有定义数组的类型,但是在调用foo方法的时候,会进行转换,比如foo的参数为int[],表达式会进行相应的转换.
五、属性访问
MVEL属性访问遵循其他语言,比如Groovy OGNL,EL等的获取属性对象。不像其他语言需要取决于底层的访问方法,MVEL提供了一个单一的、统一的语法来访问属性,map,list,等等。
在java中,我们通过以以下方式获取数据
user.getManager().getName();
在mvel中你可以使用如下方式
user.manager.name
你还可以进行控制空值
在java中:
if (user.manager != null) { return user.manager.name; } else { return null; }
在mvel中
user.?manager.name
访问arraylist
mvel中
user[6]user.get(6)
访问map:
mvel中
user["foobar"]
java中
user.get(foobar)
For
Maps
that use a String as a key, you may use another special syntax:
user.foobar
当Map的key是String类型时,还可以使用特殊的方式来访问,如:user.foobar,也就是允许你把map本身看成一个虚拟的对象,来访问其属性
字符串作数组
为了能使用属性的索引(迭代也是如此),所有的字符串都可以看成是一个数组,在MVEL中你可以用下面的方式来获取一个字符串变量的第一个字符:
foo = "My String";
foo[0]; // returns 'M'
6 MVEL 2.0 Literals
A literal is used to represent a fixed-value in the source of a particular script.
六、字面量literals
String literals
在脚本语言中,一段文字用来代表一个固定的值
字符串常量:
字符串常量可以用一对单引号或一对双引号来界定。如:
"This is a string literal" 'This is also string literal'
字符串中的特殊字符
- \\ - Double escape allows rendering of single backslash in string.
- \n - Newline
- \r - Return
- \u#### - Unicode character (Example: \uAE00)
- \### - Octal character (Example: \73)
数字常量
整数可以表示为十进制(基数为10),8进制(基数为8),或十六进制(基数为16)。
一个十进制数字,不从零开始(相对于8进制、16进制而言),可以表示任意数,如:125
125 // decimal
一个八进制数,以0为前缀,后面跟着0到7内的数字
0353 // octal
一个十六进制,以0X为前缀,后面可以跟着0-9,A-F范围内的数字
0xAFF0 // hex
浮点常量
A floating point number consists of a whole number and a factional part denoted by the point/period character, with an optional type suffix.
10.503 // a double 94.92d // a double 14.5f // a float
二进制常量
You can represent BigInteger and BigDecimal literals by using the suffixes B and I(uppercase is mandatory).
104.39484B // BigDecimal 8.4I // BigInteger
布尔常量
Boolean literals are represented by the reserved keywords true and false.
Null 常量
The null literal is denoted by the reserved keywords null or nil.
内部类
和java类似,访问内部类:$
org.proctor.Person$BodyPart
七、控制流程
if和else 和java相同
if (var > 0) { System.out.println("Greater than zero!"); } else if (var == -1) { System.out.println("Minus one!"); } else { System.out.println("Something else!"); }
支持三目运算符
var > 0 ? "Yes" : "No";
嵌套的三目运算
var > 0 ? "Yes" : (var == -1 ? "Minus One!" : "No")
迭代:
count = 0; foreach (name : people) { count++; System.out.println("Person #" + count + ":" + name); } System.out.println("Total people: " + count);
支持字符串
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]
支持数字:
foreach (x : 9) { System.out.print(x); }
现在也支持for关键字
For Loop
MVEL 2.0 implements standard C for-loops:
for (int i =0; i < 100; i++) { System.out.println(i); }
Do While, Do Until
和java中的一样,MVEL也实现了Do While,Do Until,While和Until意义正好相反。
do { x = something(); } while (x != null);
... is semantically equivalent to ...
do { x = something(); } until (x == null);
While, Until
MVEL 2.0 implements standard while, with the addition of the inverse until.
while (isTrue()) { doSomething(); }
... or ...
until (isFalse()) { doSomething(); }
八、投影和交集
假设user是一个集合对象,要获取user集合中所有parent的name可以通过以下途径
parentNames = (parent.name in users);
假设user对象有一个集合成员familyMember,你可以访问该集合
familyMembers = (name in (familyMembers in users));
九、赋值
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";
Filters
你可以进行过滤
(doSomeMethod() in listOfThings if $.shouldBeRun())
$代表了前面迭代中的每一个item
你也可以用它作为一个返回成员
($ 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可以使用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提供了在编译后的表达式里使用拦截器的功能,这对实现监听器或是在表达式内部触发一个外部事件特别有用。声明拦截器用的是@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处理类的引用的方式。
https://github.com/imona/tutorial/wiki/MVEL-Guide
2019-11-19 19:47:27
共有0条评论!