Java 表达式引擎选型调研分析
2.1 AviatorScript
2.2 MVEL (MVFLEX Expression Language)
2.3 OGNL (Object-Graph Navigation Language)
2.4 SpEL (Spring Expression Language)
2.5 QLExpress
2.6 JEXL (Java Expression Language)
2.7 JUEL (Java Unified Expression Language)
2.8 Janino
2.9 其他
3.1 社区支持情况
3.2 引入大小和依赖
+- org.mvel:mvel2:jar:2.5.0.Final:compile
+- com.googlecode.aviator:aviator:jar:5.3.3:compile
+- com.alibaba:QLExpress:jar:3.3.1:compile
| +- commons-beanutils:commons-beanutils:jar:1.8.2:compile
| | \- (commons-logging:commons-logging:jar:1.1.1:compile - omitted for conflict with 1.2)
| \- commons-lang:commons-lang:jar:2.4:compile
+- org.codehaus.janino:janino:jar:3.1.10:compile
| \- org.codehaus.janino:commons-compiler:jar:3.1.10:compile
+- ognl:ognl:jar:3.4.2:compile
| \- org.javassist:javassist:jar:3.29.2-GA:compile
+- org.apache.commons:commons-jexl3:jar:3.3:compile
| \- commons-logging:commons-logging:jar:1.2:compile
+- org.springframework:spring-expression:jar:5.3.29:compile
| \- org.springframework:spring-core:jar:5.3.29:compile
| \- org.springframework:spring-jcl:jar:5.3.29:compile
+- de.odysseus.juel:juel-api:jar:2.2.7:compile
+- de.odysseus.juel:juel-impl:jar:2.2.7:compile
+- de.odysseus.juel:juel-spi:jar:2.2.7:compile
3.3 性能
3.3.1 字面量表达式
3.3.2 含有变量的表达式
-
由于不同的表达式引擎在执行第2个表达式时底层实现除法时有所差别,MVEL,AviatorScript,JEXL 执行decimal.divide(otherDecimal, java.math.MathContext.DECIMAL128),其他实际执行的是decimal.divide(otherDecimal, scale, roundingMode),只是参数略有不同,分析时分组进行。
-
由于QlExpress执行第3个表达式时报错,不支持非整型mod操作,需要增加类型转换,实际执行的是i * pi + (d * b - 199) / (1 - d * pi) - (int)(2 + 100 - i / pi) % 99 == i * pi + (d * b - 199) / (1 - d * pi) - (int)(2 + 100 - i / pi) % 99
-
由于AviatorScript执行第4个表达式时报错,null的字面量是nil,实际执行的是(clientVersion == '1.9.0' || clientVersion == '1.9.1' || clientVersion == '1.9.2') && deviceType == 'Xiaomi' && weight >= 4 && osVersion == 'Android 9.0' && osType == 'Android' && clientIp != nil && requestTime <= now&& customer.grade > 1 && customer.age > 18
结果分析:
第1个基本类型包装类的算术计算 SpEl 最优。其次是AviatorScript,MVEL,OGNL。而JEXL,JUEL,QlExpress则不如其他引擎。
第2个BigDecimal类型的算术计算。由于底层实现不同,分为两组。第1组 MVEL、AviatorScript和JEXL,AviatorScript 优于 MVEL 优于 JEXL。第2组 JUEL,QlExpress,OGNL和SpEl,性能由优到差依次是 OGNL,SpEl,JUEL,QlExpress。并且第1组由于精度更高,性能明显都差于第2组。
第3个含有基本类型包装类算数计算的布尔表达式。SpEl 最优,AviatorScript 次之,接下来依次是 OGNL, MVEL,JUEL,JEXL,QlExpress。
第4个含有字符串比较的布尔表达式。AviatorScript,MVEL,JEXL,OGNL 性能优于 JUEL,QlExpress,SpEl。
3.3.3 含有方法调用的表达式
:new java.util.Date()
:s.substring(b.d)
:s.substring(b.d).substring(a, b.c.e)
说明:
-
由于 JUEL 执行new java.util.Date()时报错,不支持new实例,本轮实际执行的是自定义函数fn:date()
-
由于 AviatorScript 执行s.substring时报错,需使用其提供的内部函数,本轮实际执行的是其内部函数string.substring
结果分析:
此轮测试中 SpEl 的表现最优,甚至比Janino还要快。MVEL,AviatorScript次之,在执行构造方法时MVEL要好于AviatorScript。JEXL 表现也比较出色。QlExpress,JUEL,OGNL这三个表达式引擎则不如其他引擎。
3.3.4 总结
综合以上测试结果,AviatorScript,SpEl,MVEL,OGNL性能表现相对较好。
AviatorScript 性能相对较好,表现均衡,但其语法相较其他引擎跟Java的差异略大。
SpEl 除了在个别场景下性能较差,大部分场景表现非常出色,尤其是在字面量和含有变量的算数计算及方法调用场景下。
MVEL 性能表现相对均衡,含有变量的算术计算略差于AviatorScript,其在字面量算术计算,方法调用场景下表现都非常出色。
OGNL 性能表现也相对均衡,但方法调用场景下表现不佳。
3.4 安全
引入表达式引擎,应该重视系统的安全性和可靠性,比如要防止在不可信环境中被注入恶意脚本,越权执行某些系统命令或使应用停止服务等。安全性方面主要通过漏洞披露、安全指南和配置比较几种表达式引擎。
3.4.1 漏洞
首先在https://cve.mitre.org/cve/search_cve_list.html通过关键字搜索的方式粗略了解一下不同表达式引擎被公开的漏洞。这种方式可能不是非常的准确,由于不同表达式引擎的使用场景、使用方式、关注度的不同可能导致被公开的漏洞存在差异。比如我们所熟悉的 OGNL、SpEl 的关键字出现在漏洞中的频率明显高于其他表达式引擎。OGNL 在MyBatis和Struts中被使用,SpEl则在Spring中被广泛使用,这两个表达式引擎会被大部分项目间接使用,直接将用户输入作为表达式的一部分执行,很容易导致出现漏洞。
我们可以从这些公布的漏洞中了解不同表达式引擎可能存在的安全隐患及其修复情况,在使用过程中尽可能避免出现类似问题。
此外,不推荐将表达式执行直接开放到不可信的环境,如果确实需要,应该详细了解选择的表达式引擎,是否提供了必要的设置选项可以避免某些安全隐患。
3.4.2 安全设置
AviatorScript,QLExpress,JEXL均从不同程度提供了一些安全选项设置。
AviatorScript
-
设置白名单
// 在new语句和静态方法调用中允许使用的类白名单 默认 null 表示无限制
AviatorEvaluator.setOption(Options.ALLOWED_CLASS_SET, Sets.newHashSet(List.class));
// 在new语句和静态方法调用中允许使用的类白名单 包含子类 默认 null 表示无限制
AviatorEvaluator.setOption(Options.ASSIGNABLE_ALLOWED_CLASS_SET, Sets.newHashSet(List.class));
-
防止死循环
// 循环最大次数 默认 0 表示无限制
AviatorEvaluator.setOption(Options.MAX_LOOP_COUNT, 10000);
-
特性开关
// 关闭某些特性
AviatorEvaluator.getInstance().disableFeature(Feature.Module);
AviatorEvaluator.getInstance().disableFeature(Feature.NewInstance);
// 只开启需要的特性
AviatorEvaluator.setOption(Options.FEATURE_SET, Feature.asSet(Feature.If));
QLExpress
-
开启沙箱模式
QLExpressRunStrategy.setSandBoxMode(true);
在沙箱模式中,不可以:
可以:
◦使用 QLExpress 的自定义操作符/宏/函数,以此实现与应用的受控交互
◦使用. 操作符获取 Map 的 key 对应的 value,比如 a 在应用传入的表达式中是一个 Map,那么可以通过 a.b 获取
◦所有不涉及应用 Java 类的操作
-
设置白名单
// 设置编译期白名单
QLExpressRunStrategy.setCompileWhiteCheckerList(Arrays.asList(
// 精确设置
CheckerFactory.must(Date.class),
// 子类设置
CheckerFactory.assignable(List.class)
));
// 设置运行时白名单// 必须将该选项设置为 true
QLExpressRunStrategy.setForbidInvokeSecurityRiskMethods(true);
// 有白名单设置时, 则黑名单失效
QLExpressRunStrategy.addSecureMethod(RiskBean.class, "secureMethod");
-
设置黑名单
// 必须将该选项设置为 true
QLExpressRunStrategy.setForbidInvokeSecurityRiskMethods(true);
// 这里不区分静态方法与成员方法, 写法一致
// 不支持重载, riskMethod 的所有重载方法都会被禁止
QLExpressRunStrategy.addSecurityRiskMethod(RiskBean.class, "riskMethod");
QLExpess 目前默认添加的黑名单有:
◦java.lang.System.exit
◦java.lang.Runtime.exec
◦java.lang.ProcessBuilder.start
◦java.lang.reflect.Method.invoke
◦java.lang.reflect.Class.forName
◦java.lang.reflect.ClassLoader.loadClass
◦java.lang.reflect.ClassLoader.findClass
-
防止死循环
//可通过timeoutMillis参数设置脚本的运行超时时间:1000ms
Object r = runner.execute(express, context, null, true, false, 1000);
JEXL
-
使用沙箱
// 使用中应该通过JexlSandbox的重载构造方法进行配置
new JexlBuilder().sandbox(new JexlSandbox()).create();
-
设置白名单权限
new JexlBuilder().permissions(JexlPermissions.RESTRICTED.compose("com.jd.*")).create();
-
特性开关
// 关闭循环、new 实例,import等特性
new JexlBuilder().features(new JexlFeatures().loops(false).newInstance(false).importPragma(false)).create();
3.5 使用案例
从业界使用情况可以了解不同表达式引擎的可行性、生态和整合性,以及最佳实践,进而借鉴。从下表可以看到AviatorScript,MVEL,QLExpress在国内业务线均有使用案例,有些企业也有文章输出,我们可以借鉴使用。
3.6 语法
易于理解和使用的语法可以提高开发效率,并降低学习成本。接下来从类型、操作符、控制语句、集合、方法定义几方面比较一下不同表达式引擎的语法设计。
类型方面,AviatorScript 设计了特有的类型,使用时需要注意其类型转换的优先级long->bigint->decimal->double。AviatorScript、MVEL、OGNL、JEXL都支持BigInteger、BigDecimal字面量,这意味着进行精确计算时可以使用字面量,将更方便,如10.24B就表示一个BigDecimal字面量(AviatorScript中BigDecimal字面量后缀是M)。此外AviatorScript、QLExpress还支持高精度计算的设置项。
操作符方面,QLExpress支持替换、自定义操作符及添加操作符别名,这可能有助于简化复杂表达式或使表达式更加直观,不过添加预置函数应该可以达到差不多的效果。AviatorScript也支持自定义部分操作符,不过支持数量相当有限。AviatorScript、SpEl、JEXL支持正则匹配操作符。
控制语句方面,除OGNL、SpEl、JUEL不支持控制语句外,其他都支持,不过需要注意 AviatorScript 的 else if 语法有些特殊写作 elsif,foreach语句跟Java也有所不同。
集合方面,除JUEL外其他都提供了快捷定义的方式,只不过语法不同。
函数定义方面,SpEl、JUEL均不支持,OGNL支持伪lambda定义,其他都支持定义函数。QLExpress不支持定义lambda。
综合来看,和Java语法都或多或少存在一些差异。AviatorScript设计了自己特有的一些语法,使用的话需要熟悉一下。QLExpress支持自定义操作符,可以使表达式看起来更直观。MVEL、JEXL的语法可能更接近Java,让人更容易接受一些。OGNL、SpEl、JUEL的语法更简单一些,不支持控制语句和函数定义,当然也可以通过预置一些函数变通解决一些较复杂的问题。
社区方面,SpEl无疑是最活跃的。AviatorScript,QLExpress,MVEL在国内很受欢迎,QLExpress 有阿里背书。
代码大小和依赖方面,AviatorScript,MVEL 依赖少,并且代码大小也偏小。
性能方面,如果你使用表达式引擎执行字面量算术计算或方法调用偏多可以选用SpEl,MVEL。如果希望整体性能表现较好可以选用 AviatorScript。
安全方面,如果想自定义安全选项,可以考虑 AviatorScript,QLExpress和JEXL。
使用案例方面,AviatorScript,MVEL,QLExpress在国内都有实际使用案例可循。
语法方面,可能存在一些主观因素,仅供参考,个人觉得MVEL、JEXL的语法设计使用起来会更容易一些。
通过对以上几个方面的评估和分析,希望可以帮助团队基于自身情况及偏好选择最适合自己项目的Java表达式引擎。
参考资料
[1] QLExpress:https://github.com/alibaba/QLExpress
[2] AviatorScript:https://github.com/killme2008/aviatorscript
[3] MVEL:https://github.com/mvel/mvel
[4] OGNL:https://github.com/orphan-oss/ognl
[5] SpEl:https://github.com/spring-projects/spring-framework
[6] Janino:https://github.com/janino-compiler/janino
[7] JUEL:https://github.com/beckchr/juel
[8] JEXL:https://github.com/apache/commons-jexl
[9] Fel:https://github.com/dbcxy/fast-el
[10] ik-expression:https://code.google.com/archive/p/ik-expression/
[11] JSEL:https://code.google.com/archive/p/lite/wikis/JSEL.wiki
[1] JMH:https://www.cnblogs.com/wupeixuan/p/13091381.html
本文分享自微信公众号 - 京东云开发者(JDT_Developers)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
JumpServer 开源堡垒机 V2 社区版即将停止维护
尊敬的JumpServer开源堡垒机用户:您好! 如《关于JumpServer开源堡垒机V2版本产品生命周期的相关说明》所示,JumpServer开源堡垒机V2版本(社区版)将于2023年12月31日停止维护支持。 在过去两年多的时间里,JumpServer开源堡垒机V2版本获得了众多用户的支持和喜爱。出于产品自身迭代和用户需求升级的要求,2023年2月27日,JumpServer开源堡垒机正式发布v3.0版本,目前已更新至v3.9.2版本。JumpServer开源项目组建议社区版和企业版用户更新至JumpServe v3.x版本,以使用更多的新增功能并获取更好的软件使用体验。 JumpServer V2版本(企业版)维护支持截止日期为2025年12月31日。 aJumpServer开源堡垒机V2版本产品生命周期具体如下,广大用户可以根据时间表合理安排系统升级及迁移工作。 ▲ JumpServer开源堡垒机V2版本产品生命周期 感谢您长期以来对JumpServer开源项目的支持与厚爱。如果您在升级过程中遇到问题,可以联系JumpServer开源项目组获取升级建议和指导。 JumpSer...
- 下一篇
Sailfish OS 开发商 Jolla 已被其前管理层收购
芬兰科技公司 Jolla 的前管理层收购了 Jolla Ltd. 的全部业务和员工。 根据 Jolla 发布的新闻稿,Jolla Ltd 专注于操作系统和汽车软件的全部业务和员工将被转移到一家新公司,这家新公司已被 Jolla 前管理层收购。 由于乌克兰战争,俄罗斯在 Jolla 集团结构中的所有权成为员工和客户面临的一个紧迫问题,最终导致该公司于 2023 年春季开始实施企业重组计划。2023 年 11 月 24 日,Pirkanmaa 地方法院就重组计划做出了决定,并责成将业务完全出售给另一家公司。目前 Jolla 的前管理层已经收购了该公司。 Jolla 是一家曾经致力于开发智能手机和平板电脑的公司,但是这些产品并没有取得成功。后来 Jolla 将重心转向了基于 Linux 的 Sailfish OS(旗鱼),并将其应用于现有设备上。Sailfish OS 是由 Jolla 在 MeeGo 基础上开发的移动操作系统。 新公司将继续致力于开发 Sailfish OS,并向全球客户销售。他们还计划将 Sailfish OS 引入新的“人工智能时代”。Jolla 还将通过自己的子公司...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- Windows10,CentOS7,CentOS8安装MongoDB4.0.16
- SpringBoot2全家桶,快速入门学习开发网站教程
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- Eclipse初始化配置,告别卡顿、闪退、编译时间过长
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- 2048小游戏-低调大师作品
- SpringBoot2更换Tomcat为Jetty,小型站点的福音
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7
- CentOS7,8上快速安装Gitea,搭建Git服务器
- CentOS6,CentOS7官方镜像安装Oracle11G