\(^_^)/ 表达式解析器(MVEL)

来源:互联网 发布:千牛淘宝卖家助手 编辑:程序博客网 时间:2024/05/17 22:50

Jeval

在运行时解析计算静态和动态表达式;支持数学,布尔,字符串,函数表达式;支持大部分的数学和布尔运算符;支持自定义函数;支持嵌套函数;支持解析;支持自定义变量解析器;

官网:http://jeval.sourceforge.net

不支持嵌套变量

 

jexel

处理大部分的数学公式和字符串,通过继承接口来实现个人所需的运算;支持自定义变量解析器;该项目一直在更新,最近一次更新是今年十月七号

wiki:http://code.google.com/p/jexel/w/list

不支持变量替换

 

aviator Aviator是直接将表达式编译成Java字节码,交给JVM去执行

支持大部分运算操作符,包括算术操作符、关系运算符、逻辑操作符、位运算符、正则匹配操作符(=~)、三元表达式?: ,并且支持操作符的优先级和括号强制优先级;支持函数调用和自定义函数;支持传入变量,支持类似a.b.c的嵌套变量访问;支持数组的访问;支持正则表达式;

http://www.blogjava.net/killme2008/archive/2010/09/07/331296.html

依赖两外两个jarcommons-beanutils和commons-logging 

 

arity

自定义函数及公式

不支持变量替换

 

Jsci

数学科学计算:包括线性方程组,统计,小波,牛顿力学。

jar包太多,不支持变量替换

 

 

IKExpressoin

支持中文变量及函数名;支持基本运算;函数运算;支持自定义函数;

不支持变量嵌套

 

JSEL

兼容 JavaScript 运算规则的简单的表达式解释引擎,可以通过Map接口,或者JavaBean给出一个变量集合,能后通过表达式从这个集合中抽取变量,再通过表达式逻辑生成你需要的数据;支持函数扩展,操作符别名定义,操作符定义

不支持变量嵌套

 

jep

支持大量的数学运算;其他功能都差不多;支持变量嵌套(顺序执行)

wiki:http://www.singularsys.com/jep/doc/html/variables.html  

 

beanshell

基本运算;变量、函数自定义;支持变量嵌套(顺序执行)  

 

Fel

支持中文变量;支持基本运算;函数运算;支持自定义函数;可调用java方法; 不支持变量嵌套

 

 

在实际项目开发中如果需要解析数学公式,无须再运用解释器模式进行设计,可以直接使用一些第三方解析工具包,它们可以统称为数学表达式解析器(Math Expression Parser, MEP),如Expression4J、Jep、JbcParser、Symja、Math Expression String Parser(MESP)等来取代解释器模式,它们可以方便地解释一些较为复杂的文法,功能强大,且使用简单,效率较好。

      下面简单介绍两个常用的基于Java语言的第三方解析工具包:

 

      (1) Expression4J

      Expression4J是一个基于Java的开源框架,它用于对数学表达式进行操作,是一个数学公式解析器,在Expression4J中可以将数学表达式存储在字符串对象中,如“f(x,b)=2*x-cos(b)”和“g(x,y)=f(y,x)*-2”等。Expression4J是高度定制的,用户可以自定义文法,其主要功能包括实数和复数的基本数学运算,支持基本数学函数(如sin、cos等函数)、复杂函数(如f(x)=2*x+5、g(x)=3*f(x+2)-x等)以及用户使用Java语言自定义的函数和文法,还可以定义函数目录(函数集)、支持XML配置文件等。目前它还不是一个十分成熟的框架,仍在不断完善中。关于Expression4J的更多资料可以参考网站:http://sourceforge.net/projects/expression4j/?source=directory

 

      (2) Jep

      Jep(JavaMathematical Expression Parser)是一个用于解析和求解数学表达式的Java类库。通过使用Jep提供的包,我们可以输入一个以字符串表示的任意数学公式,然后立即对其进行 求解。Jep支持用户自定义变量、常量和自定义函数,同时还包含了大量通用的数学函数和常量。关于Jep的更多资料可以参考网

 

 

groovy

Aviator

BeanShell

EL

FEL

IKExpression

jep

JavaCC

JEval

JEXL

JParsec

JSEL

JXPath

MVEL

Ognl

rhino

SimpleEL

Spads

expression-analyzer

 

 

 

 

 

 

 

 

 

 

 

 

MVEL是一个功能强大的基于Java应用程序的表达式语言。

目前最新的版本是2.0,具有以下特性:

1. 动态JIT优化器。当负载超过一个确保代码产生的阈值时,选择性地产生字节代码,这大大减少了内存的使用量。

新的静态类型检查和属性支持,允许集成类型安全表达。

2. 错误报告的改善。包括行和列的错误信息。

3. 新的脚本语言特征。MVEL2.0 包含函数定义,如:闭包,lambda定义,标准循环构造(for, while, do-while, do-until…),空值安全导航操作,内联with-context运营 ,易变的(isdef)的测试运营等等。

4. 改进的集成功能。迎合主流的需求,MVEL2.0支持基础类型的个性化属性处理器,集成到JIT中。

5. 更快的模板引擎,支持线性模板定义,宏定义和个性化标记定义。

6. 新的交互式shell(MVELSH)。

 

MVEL是一种用于Java应用程序,类似于OGNL的表达式语言。

MVEL不仅非常小和敏捷,而且它的语法易于阅读与EL 或OGNL比起来更像Java。

比如静态方法和属性的引用方式与Java一样,赋值也非常像Java

 

MVEL为 MVFLEX Expression Language(MVFLEX表达式语言)的缩写,它是一种动态/静态的可嵌入的表达式语言和为Java平台提供Runtime(运行时)的语言。

最初是作为一个应用程序框架实用程序的语言开始,该项目现已发展完全独立。

MVEL通常用于执行用户(程序员)通过配置XML文件或注释等定义的基本逻辑。

它也可以用来解析简单的JavaBean表达式。Runtime(运行时)允许MVEL表达式通过解释执行或者预编译生成字节码后执行。

 

 

 

MVEL最初是Mike Brock的Valhalla项目的表达计算器。Valhalla本身是一个早期的Seam,就像为自动化“out of the box”web应用而生的框架,虽然Valhalla现在是休眠状态,但是MVEL仍旧作为活跃的开发项目向前发展。通常,我们会将MVEL同OGNL、JEXL、JUEL这样的项目作对比;不论是性能、特性还是易用性,尤其是集成方面,MVEL都已经远远超过那些项目。MVEL还没有尝试另一种JVM语言,但是开始关注解决嵌入式脚本的问题。

由于内存限制或者沙盒不能用字节码生成的约束环境中,MVEL则十分理想。取代尝试再创在Java,相反,MVEL目的在于为Java程序员提供一种类似的语法,同时也为短的和简明表达式添加了语法糖衣。

MVEL是Drools规则引擎的一部分,很多密封集成点式同Drools团队一同开发的。那会其他脚本语言可以再次进行复查,但是也显示了一下的问题:

缺少可选类型安全

集成不良,通常通过映射填入内容。没有字节码不能运作用字节码生成编译时间慢,还增加了可扩展性问题;不用字节码生成运行时执行非常慢

内存消耗过大

Jar巨大/依赖规模

 

 

 

 

 

 

官网

http://mvel.codehaus.org/

 

下载:

http://repository.codehaus.org/org/mvel/mvel2/2.2.0.Final/

https://github.com/mvel/mvel

 

OGNL 2.7.2 、JEXL 1.1、JUEL 2.1.0、Groovy 1.5.7、MVEL 2.0性能测试 

http://www.iteye.com/topic/361794

 

mvel语法指南

http://blog.csdn.net/y461517142/article/details/17926055

http://blog.csdn.net/fhm727/article/details/6543152

 

Ognl/MVEL/Aviator/JSEL 四种表达式引擎执行效率对比

http://jindw.iteye.com/blog/732354

 

OGNL & MVEL &java调用 性能误解

http://caoyaojun1988-163-com.iteye.com/blog/2089726

 

 

 

虽然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";

 

 

 

 

十、

EL可以使用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处理类的引用的方式。

原创粉丝点击