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巨大/依赖规模
属性访问函数和数据结构
MVEL支持类javascript语法,这是我们熟悉并喜欢的表达语言,支持简化的属性访问函数,内联映射以及列表:
以下是引用片段:
[1, 2, 3, 4] // array
["key1" : 1, "key2" : 3 ] // map
person.name // simple property accessor
person.pets["rover"].age // map accessor
person.pets["rover"].?age // null safe accessor
使用“with”声明,并内联函数构造器,下面是等价功能:以下是引用片段:
with ( new Person() ) {
name = "Bobba Fet", age = "50"
}
new Person().{
name = "Bobba Fet", age = "50"
}
也可以对复杂数据结构创建使用内联映射和列表语法:以下是引用片段:
[ people : [
new Person().{ name = "Bobba Fet" },
new Person().{ name = "Darth Vader" }
],
places : [
new Place().{ name = "Death Star" }
]
]
在MVEL中就像其他语言一样返回值,默认的是最后一个值。此外,你也可以使用“return”关键词从程序中手动返回。以下是引用片段:
if (val) { “foo”}
else
{ “bar”};
Is the same as:
if (val) { return “foo” }
else
{ return “bar” };
这个例子中通过关键词“room”和“doors”返回一个Map,Door参照了两个room Map中以前创建的实例
以下是引用片段:
rooms = [
"basement" : new Room("basement"),
"lounge" : new Room("kitchen")
];
// return is implicit,
// but can be explicitly added if preferred
doors = [
new Door(
rooms["kitchen"], rooms["basement"]
)
];
[ "rooms" : room, "doors" : doors ];
外部变量词典
MVEL可以使用Map计算给定的表达,提供一个可用变量词典:
以下是引用片段:
Map vars = new HashMap();
vars.put( "person", p );
// p is a previous created instance
//p.pets references a Map.
Dog dog =
( Dog ) MVEL.eval( "person.pets['rover']", vars );
上面所说的将会使用MVEL的“interpreted”模式,快速单程编译和执行String。它也能为更快的执行进行预编译。
MVEL通过性能优化的分析器开发,保持了jar大小,而且意味着MVEL不要求任何附加的外部依赖。Jar大概700kb左右,包括调试信息。编译声明仍旧会被解读,使用一种优化的内部执行图表。对于性能敏感的代码,MVEL的优化程序可以发出字节码来加速域和方法的访问。
编译例子:
以下是引用片段:
ExecutableStatement stmt =
MVEL.compileExpression( "person.pets['rover']" );
MVEL.executeExpression( stmt, vars );
内容对象可用来执行脚本,而不是提供对象。注意下面的例子中,我们可以直接访问“pet的属性”,这对于Person的方法也同样有效:
以下是引用片段:
Dog dog =
( Dog ) MVEL.eval( "pets['rover']", p );
内容对象和变量词典可以共同使用:
以下是引用片段:
vars.put( "otherPet", patch )
// patch is another pet
MVEL.eval(
"pets['rover'].age == otherPet.age", p, vars
);
输入声明可行,“*”也可以成套引进使用:
以下是引用片段:
"import org.domain.Dog; pets['rover'] == new Dog('patch', 7)"
类输入可以通过可重用的ParserConfiguration自动添加,如果这个输入可以被编译中的大多数表达所共享,ParserConfiguration相当不错。以下是引用片段:
ParserConfiguration pconf =
new ParserConfiguration();
pconf.addImport( "Dog", Dog.class );
ParserContext pctx = new ParserContext( pconf );
MVEL.compileExpression(
"pets['rover'] == new Dog('patch', 7)", pctx
);
输入信息和输入安全
ParserContext在外部变量编译时间可随意用于提供输入信息。在哪里提供输入信息,编译器就可以更好的假设,结果就是更快地执行性能。局部变量部署支持类型推断,如果他们可以推断出类型就可以优化。以下是引用片段:
ParserContext pctx = new ParserContext( );
pctx.addInput( “p”, Person.class );
MVEL.compileExpression( "p.pets['rover']", pctx );
此外,MVEL已经能够以非类型安全的方式动态执行,这一点类似其他的表达语言,像OGNL或者JEXL。但是编译要确保所有输入安全,使用可选的强键入。如果输入信息不可用或者不能推断,编译就会失败。
以下是引用片段:
ParserContext pctx = ParserContext.create()
.stronglyTyped()
.withInput(“p”, Person.class);
MVEL.compileExpression( "p.pets['rover']", pctx );
Setting the type of the context object can be done via the 'this' input:
ParserContext pctx = ParserContext.create()
.stronglyTyped()
.withInput( “this”, Person.class );
MVEL.compileExpression( "this.toString()", pctx );
完全的输入推断是为局部变量提供的,所以输入声明或者类型转换的支持是可选的。MVEL也可以从可用的泛型中推断输入信息:以下是引用片段:
public static class Person {
private Map pets;
...
}
...
ParserContext pctx = ParserContext.create()
.stronglyTyped();
.withInput( “person”, Person.class );
//Both the following MVEL statements are valid and type safe:
MVEL.compileExpression( "Pet rover = (Pet) person[“rover”];n”+
“return rover.age;", pctx );
MVEL.compileExpression( "rover = person[“rover”];n”+
“return rover.age;", pctx );
MVEL比较有意思的一项性能是提供分析表达式的功能,而且可以告诉你外部输入,也就是变量不是本地分配的。也可以用于为局部变量报告推断输入。确定了外部输入时,强键入就不能使用,而且输入的内容将会被作为java.lang.Object处理。以下是引用片段:
ParserContext pctx = ParserContext.create();
MVEL.compileExpression( "person.pets['rover']", pctx );
// Map returns [“person” : Object.class]
Map inputs = pctx.getInputs();
从局部变量获取输入:
以下是引用片段:
ParserContext pctx = ParserContext.create()
.stronglyTyped()
.withInput( “person”, Person.class );
MVEL.compileExpression( "pet = person.pets['rover']", pctx );
// Map returns [“pet” : Pet.class]
Map inputs = pctx.getVariables();
索引变量词典
对外部变量词典使用Map是嵌入式脚本语言最常用的方式,但是并不是性能的最优化方式。这回消耗过多的内存,而且每一次读或者写就是一个Map put/get。MVEL的最新版本通过配置属性“indexAllocation”,引入了预计算索引变量,所以读和写变量和访问矩阵一样快速,在内存消耗上不会比矩阵匹配的变量长度需求多。索引也可以用于局部变量,确保高性能。以下是引用片段:
ParserContext pctx = new ParserContext( )
.stronglyTyped()
.withInput(“person”, Person.class)
.withInput(“otherPet”, Person.class)
String[] varNames = new String[] { “person”, “otherPet” };
pctx.addIndexedInput(varNames);
pctx.setIndexAllocation(true);
String exp = “person.pets['rover'].age == otherPet.age”;
SharedVariableSpaceModel model =
VariableSpaceCompiler.compileShared(expr, ctx);
ExecutableStatement stmt = MVEL.compileExpression( expr, pctx );
// Notice the values order must
// match the indexedInput variable name order
Object[] values = new Object[] { person, patch };
MVEL.executeExpression(stmt, model.createFactory(values) );
增强功能可选字节码生成
高度优化实时编译器
编写优化
逃逸分析和冗余优化
总结
到现在你应该可以确信,作为Java开发者你不能把MVEL拿出你的工具盒了吧,MVEL填补了完成编程环境之间独特的代沟,像Groovy和Scala和表达语言OGNL和JUEL。就算全世界在讨论Gavin King的Ceylon项目的各种优点,他们错过了Jboss的秘密武器。