该帖已经被评为精华帖
|
|||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
作者 | 正文 | ||||||||||||||||||||||||||||||||
发表时间:2010-06-29
最后修改:2010-06-30
《飞行大亨》是我很喜欢的一部电影,不过这里我想介绍的是一个叫Aviator的开源的Java表达式求值器。 一、轮子的必要性
我将Aviator定位在Groovy这样全功能的脚本和IKExpression这样的简易的表达式求值之间的东西,如果你不希望带上Groovy那么庞大的jar却只用上一点点的功能,如果你希望功能和性能上比IKExpression好那么一些,那么也许你可以考虑Aviator。 Aviator的设计思路跟利用GroovyObject的求值是一样,通过编译并动态生成字节码的方式将表达式编译成一个类,然后反射执行这个类,因此会在效率上比纯解释执行的IKExpression好一些。
二、让轮子转起来。
求算术表达式:
import com.googlecode.aviator.AviatorEvaluator; public class SimpleExample { public static void main(String[] args) { Long result = (Long) AviatorEvaluator.execute("1+2+3"); System.out.println(result); } } 执行入口统一为AviatorEvaluator类,它有一系列静态方法。
逻辑表达式和关系运算: AviatorEvaluator.execute("3>1 && 2!=4 || true");
Aviator支持所有的关系运算符和算术运算符,不支持位运算,同时支持表达式的优先级,优先级跟Java的运算符一样,并且支持通过括号来强制优先级。
使用变量和字符串相加: String yourname = “aviator”; Map<String, Object> env = new HashMap<String, Object>(); env.put("yourname", yourname); String result = (String) AviatorEvaluator.execute(" 'hello ' + yourname ", env); System.out.println(result); 打印: hello aviator
字符串可以单引号也可以双引号括起来,并且支持转义字符。变量名称只要是合法的java identifer即可,变量需要用户传入,通过Map<String,Object>指定变量名和值是什么,这里的变量是yourname。
变量的访问支持嵌套访问,也就是dot操作符来访问变量里的属性,假设我们有一个Foo类:
public static class Foo { int i; float f; Date date = new Date(); public Foo(int i, float f, Date date) { super(); this.i = i; this.f = f; this.date = date; } public int getI() { return i; } public void setI(int i) { this.i = i; } public float getF() { return f; } public void setF(float f) { this.f = f; } public Date getDate() { return date; } public void setDate(Date date) { this.date = date; } }
然后在使用一个表达式来描述Foo里的各种属性:
Foo foo = new Foo(100, 3.14f, new Date()); Map<String, Object> env = new HashMap<String, Object>(); env.put("foo", foo); String result = (String) AviatorEvaluator.execute( " '[foo i='+ foo.i + ' f='+foo.f+' year='+(foo.date.year+1900)+ ' month='+foo.date.month +']' ", env); 我们可以通过foo.date.year的方式来访问变量foo中date属性的year值,这是利用commons-beanutils的反射功能实现的,前提是你的变量是合法的JavaBean(public、getter缺一不可)。
访问集合和数组: import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import com.googlecode.aviator.AviatorEvaluator; public class CollectionExample { public static void main(String[] args) { final List<String> list = new ArrayList<String>(); list.add("hello"); list.add(" world"); final int[] array = new int[3]; array[0] = 0; array[1] = 1; array[2] = 3; final Map<String, Date> map = new HashMap<String, Date>(); map.put("date", new Date()); Map<String, Object> env = new HashMap<String, Object>(); env.put("list", list); env.put("array", array); env.put("map", map); System.out.println(AviatorEvaluator.execute( "list[0]+list[1]+'\narray[0]+array[1]+array[2]='+(array[0]+array[1]+array[2]) +' \ntoday is '+map.date ", env)); } } 可以通过[]中括号去访问java.util.List和数组中的元素,可以通过map.key访问map中的key对应的value。
日期比较,aviator支持String跟java.util.Date对象直接比较,要求String是形如"yyyy-MM-dd HH:mm:ss:SS"这样的字符串,一个例子 Map<String, Object> env = new HashMap<String, Object>(); final Date date = new Date(); String dateStr = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SS").format(date); env.put("date", date); env.put("dateStr", dateStr); Boolean result = (Boolean) AviatorEvaluator.execute("date==dateStr", env); System.out.println(result); result = (Boolean) AviatorEvaluator.execute("date > '2009-12-20 00:00:00:00' ", env); System.out.println(result); result = (Boolean) AviatorEvaluator.execute("date < '2200-12-20 00:00:00:00' ", env); System.out.println(result); result = (Boolean) AviatorEvaluator.execute("date ==date ", env); System.out.println(result);
三元表达式: AviatorEvaluator.execute("3>0? 'yes':'no'");
上面都还是一个求值器表达式的常见功能,下面要描述的是Aviator的一些偏脚本性的功能。
类Ruby、Perl的正则匹配,匹配email地址: AviatorEvaluator.execute("'killme2008'=~/([\\w0-8]+@\\w+[\\.\\w+]+)/ "); 成功的话返回true,否则返回false。//括起来的字符序列成为正则表达式,=~操作符用于匹配。匹配只能在String和Pattern之间。 匹配成功,获得匹配的分组,利用变量$digit:
AviatorEvaluator.execute("'killme2008@gmail.com'=~/([\\w0-8]+@\\w+[\\.\\w+]+)/ ? $1:'unknow'"); 匹配成功返回$1,表示第一个匹配的分组,也就是用户名 killme2008
函数调用: AviatorEvaluator.execute("sysdate()"); sysdate()是一个内置函数,返回当前日期,跟new java.util.Date()效果相同。
更多内置函数: AviatorEvaluator.execute("string.length('hello')"); // 求字符串长度 AviatorEvaluator.execute("string.contains('hello','h')"); //判断字符串是否包含字符串 AviatorEvaluator.execute("string.startsWith('hello','h')"); //是否以子串开头 AviatorEvaluator.execute("string.endsWith('hello','llo')"); 是否以子串结尾 AviatorEvaluator.execute("math.pow(-3,2)"); // 求n次方 AviatorEvaluator.execute("math.sqrt(14.0)"); //开平方根 AviatorEvaluator.execute("math.sin(20)"); //正弦函数
可以看到Aviator的函数调用风格非常类似lua或者c。
自定义函数,实现AviatorFunction接口并注册即可,比如我们实现一个add函数用于相加: import com.googlecode.aviator.AviatorEvaluator; import com.googlecode.aviator.runtime.function.FunctionUtils; import com.googlecode.aviator.runtime.type.AviatorDouble; import com.googlecode.aviator.runtime.type.AviatorFunction; import com.googlecode.aviator.runtime.type.AviatorObject; class AddFunction implements AviatorFunction { public AviatorObject call(Map<String, Object> env, AviatorObject... args) { if (args.length != 2) { throw new IllegalArgumentException("Add only supports two arguments"); } Number left = FunctionUtils.getNumberValue(0, args, env); Number right = FunctionUtils.getNumberValue(1, args, env); return new AviatorDouble(left.doubleValue() + right.doubleValue()); } public String getName() { return "add"; } } 注册并调用: AviatorEvaluator.addFunction(new AddFunction()); System.out.println(AviatorEvaluator.execute("add(1,2)")); System.out.println(AviatorEvaluator.execute("add(add(1,2),100)")); 函数可以嵌套调用。
三、不公平的性能测试
基本介绍完了,最后给些测试的数据,下列的测试场景都是每个表达式预先编译,然后执行1000万次,测量执行耗时。
场景1: 算术表达式 1000+100.0*99-(600-3*15)/(((68-9)-3)*2-100)+10000%7*71 结果:
场景2: 计算逻辑表达式和三元表达式混合: 6.7-100>39.6 ? 5==5? 4+5:6-1 : !(100%3-39.0<27) ? 8*2-199: 100%3 测试结果:
场景3: 计算算术表达式和逻辑表达式的混合,带有5个变量的表达式: i * pi + (d * b - 199) / (1 - d * pi) - (2 + 100 - i / pi) % 99 ==i * pi + (d * b - 199) / (1 - d * pi) - (2 + 100 - i / pi) % 99 变量设定为: int i = 100; float pi = 3.14f; double d = -3.9; byte b = (byte) 4; boolean bool=false; 每次执行前都重新设置这些变量的值。 结果:
场景4:
结果:
原始的测试报告在这里。
四、结语
能看到这里,并且感兴趣的朋友请点击项目主页: http://code.google.com/p/aviator/
下载地址: http://code.google.com/p/aviator/downloads/list
完整的用户手册:
http://code.google.com/p/aviator/wiki/User_Guide_zh 目前版本仍然是1.0.0-RC,希望更多朋友试用并最终release。有什么疑问或者建议请跟贴。
声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|||||||||||||||||||||||||||||||||
返回顶楼 | |||||||||||||||||||||||||||||||||
发表时间:2010-06-29
有点意思,是不是可以理解为java的eval函数?
请问是不打算支持位运算还是暂时不支持位运算? |
|||||||||||||||||||||||||||||||||
返回顶楼 | |||||||||||||||||||||||||||||||||
发表时间:2010-06-29
最后修改:2010-06-29
berlou 写道 有点意思,是不是可以理解为java的eval函数?
请问是不打算支持位运算还是暂时不支持位运算? 这个东西目前还只是满足我的需求,如果以后有人用可以考虑添加。不仅仅是eval,有些语法不是java的,只是一个小集合。 |
|||||||||||||||||||||||||||||||||
返回顶楼 | |||||||||||||||||||||||||||||||||
发表时间:2010-06-30
似乎没啥人感兴趣,悲剧。
|
|||||||||||||||||||||||||||||||||
返回顶楼 | |||||||||||||||||||||||||||||||||
发表时间:2010-06-30
最后修改:2010-06-30
研究研究
这玩意做规则引擎是必须的 |
|||||||||||||||||||||||||||||||||
返回顶楼 | |||||||||||||||||||||||||||||||||
发表时间:2010-06-30
我感兴趣!
不过我已经比较习惯OGNL表达式,你的这个可以支持类似OGNL表达式中的一些功能吗?比如操作Collection,投影等等。 OGNL的API实在不友好,在普通环境上使用会让人崩溃。但是看到你这个API,我感觉很能符合我的要求。 |
|||||||||||||||||||||||||||||||||
返回顶楼 | |||||||||||||||||||||||||||||||||
发表时间:2010-06-30
downpour 写道 我感兴趣!
不过我已经比较习惯OGNL表达式,你的这个可以支持类似OGNL表达式中的一些功能吗?比如操作Collection,投影等等。 OGNL的API实在不友好,在普通环境上使用会让人崩溃。但是看到你这个API,我感觉很能符合我的要求。 操作collection可以作为库函数添加,这不是问题,主要是我精力有限,只能实现一些常用的内置函数,我有空搞下collection的库。 投影是什么意思? |
|||||||||||||||||||||||||||||||||
返回顶楼 | |||||||||||||||||||||||||||||||||
发表时间:2010-06-30
AviatorEvaluator.execute是static?最好还是不要做成static
|
|||||||||||||||||||||||||||||||||
返回顶楼 | |||||||||||||||||||||||||||||||||
发表时间:2010-06-30
性能能超过MVEL就好了
|
|||||||||||||||||||||||||||||||||
返回顶楼 | |||||||||||||||||||||||||||||||||
发表时间:2010-06-30
chandler 写道 AviatorEvaluator.execute是static?最好还是不要做成static
什么理由?还没release,接口还可以变更。 |
|||||||||||||||||||||||||||||||||
返回顶楼 | |||||||||||||||||||||||||||||||||