流行的表达式引擎简单分析对比

本文介绍了在Java中针对设备能耗监测需求进行的表达式引擎性能测试,包括Aviator、Jexl3、GraalVM JavaScript、MVEL和Spring EL等。测试结果显示GraalVM性能最佳,且具备多语言支持。作者决定选用GraalVM,因为它不仅速度快,还有Oracle和Eclipse的背书,适合后期扩展业务逻辑。
摘要由CSDN通过智能技术生成


       最近来了个能耗监测的需求,也就是对设备上报数据做一些业务的计算后阈值预警风控类的。对需求进行抽丝剥茧的拆解后,发现除去业务,最难的点也就是阈值比较了,到此有经验的码农很容易就想到了表达式计算吧。基本的规则增删改查、逻辑运算表达式生成做完后,就要着手啃最难的骨头了。考虑到设备上报数据的量,肯定要考虑性能,所以先做表达式的选型。

一、依赖引入

       这里统一把我这边准备选用的表达式引擎依赖包都导入,后面就不单个说明,每个引入看引入代码上面的一行注释。另外一些老掉牙的引擎,没有维护的引擎,我这里就是直接pass了,要看引擎的活力,在maven找它的jar时看更新时间、周期。
这里再插一句CSDN浏览器插件真香,就说在maven中心库找jar吧。CSDN浏览器插件提供的shift + c调出插件,输入mvn,立刻就可以输入jar的名称或骨架名称,回车,jar就出来了,上个效果图吧。
在这里插入图片描述

 <!-- 好用的工具类合集 -->
<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.7.13</version>
</dependency>
<!-- aviator表达式引擎支持 -->
<dependency>
    <groupId>com.googlecode.aviator</groupId>
    <artifactId>aviator</artifactId>
    <version>5.2.7</version>
</dependency>

<!-- jexl3表达式引擎支持 -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-jexl3</artifactId>
    <version>3.2.1</version>
</dependency>

<!-- javascript-graalvm支持 -->
<dependency>
    <groupId>org.eclipse.dirigible</groupId>
    <artifactId>dirigible-engine-javascript-graalvm</artifactId>
    <version>5.12.0</version>
</dependency>

<!-- graalvm js支持 -->
<!-- <dependency>
    <groupId>org.graalvm.js</groupId>
    <artifactId>js</artifactId>
    <version>21.3.0</version>
</dependency>-->

<!-- mvel支持 -->
<dependency>
    <groupId>org.mvel</groupId>
    <artifactId>mvel2</artifactId>
    <version>2.4.13.Final</version>
</dependency>

这里说明下:

  1. hutool是因为我要用到它的表达式引擎封装工具
  2. javascript-graalvm版本,本来最新版本都是6.1.2了,这里用的5.12.0是因为我的JDK是8,各位博友如果是高版本的可以用6+,可能获取引擎的方式不太一样,可以查api
  3. graalvm js注释了,是因为javascript-graalvm里依赖了它,可以点进去看

二、性能测试demo

       这里今天是做纯表达式的性能测试,因为目前我这边需求只需要一行逻辑运算表达式做阈值校验就ok了。实际上有的引擎功能非常强大,可以直接就支持部分语言的整个方法执行的。比如graalvm,这个是oracle现在强烈推荐的,非常牛逼,最后总结的时候,我说对于它的想法。
测试demo就是对表达式做值替换后的逻辑运算符的布尔计算结果,表达式如下:
((dayUse > 3 && dayUse < 7 ) || (aloneUse > 100 || aloneUse < 0 )) && (totalUse > 1000 )

@Test
void checkExpre() throws Exception {
    log.info("--表达式校验测试--");

    //通用条件、参数处理
    Object eval = null;
    String orginExpre = "((dayUse > 3 && dayUse < 7 ) || (aloneUse > 100 || aloneUse < 0 )) && (totalUse > 1000 ) ";
    AtomicReference<String> expre = new AtomicReference<>(orginExpre);
    Dict dict = Dict.create().set("dayUse", 4).set("aloneUse", 3).set("totalUse", 1);

    dict.entrySet().stream().forEach(t -> {
        String tmpExpre = expre.get().replace(t.getKey(), t.getValue().toString());
        expre.set(tmpExpre);
    });

    log.info("-----纯表达式性能测试开始----");

    StopWatch sw = new StopWatch();
    sw.start();
    eval = ExpressionUtil.eval(expre.get(), dict);
    sw.stop();
    log.info("--aviator--expre:{} = {},耗时:{}ms", expre.get(), eval, sw.getTotalTimeMillis());

    //获取js引擎实例
    sw = new StopWatch();
    sw.start();
    ScriptEngineManager sem = new ScriptEngineManager();
    ScriptEngine engine = sem.getEngineByName("javascript");
    eval = engine.eval(expre.get());
    sw.stop();
    log.info("--ScriptEngine--expre:{} = {},耗时:{}ms", expre.get(), eval, sw.getTotalTimeMillis());

    sw = new StopWatch();
    sw.start();
    engine = new JexlScriptEngine();
    eval = engine.eval(expre.get());
    sw.stop();
    log.info("--Jexl3--expre:{} = {},耗时:{}ms", expre.get(), eval, sw.getTotalTimeMillis());

    sw = new StopWatch();
    sw.start();
    ExpressionParser p = new SpelExpressionParser();
    Expression exp = p.parseExpression(expre.get());
    eval = exp.getValue();
    sw.stop();
    log.info("--spel--expre:{} = {},耗时:{}ms", expre.get(), eval, sw.getTotalTimeMillis());

    sw = new StopWatch();
    sw.start();
    GraalJSEngineFactory graalJSEngineFactory = new GraalJSEngineFactory();
    GraalJSScriptEngine graalJSScriptEngine = graalJSEngineFactory.getScriptEngine();
    eval = graalJSScriptEngine.eval(expre.get());
    sw.stop();
    log.info("--graalvm--expre:{} = {},耗时:{}ms", expre.get(), eval, sw.getTotalTimeMillis());

    sw = new StopWatch();
    sw.start();
    Context context = Context.newBuilder().allowAllAccess(true).build();
    eval = context.eval("js", expre.get());

    log.info("--graalvm js 方式1--expre:{} = {},耗时:{}ms", expre.get(), eval, sw.getTotalTimeMillis());

    sw = new StopWatch();
    sw.start();
    ScriptEngine eng = new ScriptEngineManager().getEngineByName("js");
    eval = eng.eval(expre.get());
    sw.stop();
    log.info("--graalvm js 方式2--expre:{} = {},耗时:{}ms", expre.get(), eval, sw.getTotalTimeMillis());

    sw = new StopWatch();
    sw.start();
    eval = MVEL.eval(orginExpre, dict);
    sw.stop();
    log.info("--MVEL--expre:{} = {},耗时:{}ms", expre.get(), eval, sw.getTotalTimeMillis());

}

三、demo执行结果

在这里插入图片描述
很明显graalvm表达式引擎确实一骑绝尘。
纯表达式(这里要说严谨)执行性能结论:
graalvm < Jexl3 < MVEL2 < spel < aviator < ScriptEngine

  1. ScriptEngine最慢,难怪jdk8之后移除了
  2. mvel有段时间没有更新了额,这里是mvel2
  3. 一行纯表达式执行,没有出现不一致,可见都可靠

四、总结

  1. 调用方法MVEL是真飘逸
  2. graalvm的Context方式是真快啊(不要怀疑,亲测屏蔽所有其他方法,依然是这个0结果,就是光一样的男人)
  3. hutool的封装的表达式工具ExpressionUtil,要是提供getEngin(“enginName”)就更nice了,现在的自定义引擎获取要强转处理异常,不够优雅
  4. graalvm多语言引擎,可以集成ruby,js,python,groovy,kotlin等,总之是很强大,而且有eclipse、oracle加持
  5. JEXL表达式语言,标准,灵活,主要是标准,这样就不会出现执行结果不一致的情况
  6. SpelExpressionParser是spring内置的,spring加持
  7. aviator高性能、轻量级的 java
    语言实现,google加持

       最后,本次简单对比就到这里,关于选用、详情情况,各位博友可自行深挖研究。
我决定选graalvm,原因如下:

  1. 毕竟有2大护法加持,虽然发展还不全面(版本迭代快)
  2. 后期可以开放一个入口,让会json、js的就可以做一些事情,比如设备指令解析、上报数据异常报警
    等等,甚至有些业务校验都可以开放到前台去写js,避免前端、后端对于数据增删改做业务校验写死代码。

       下次有驱动场景,再分享更高级的表达式校验、场景引入等等,希望能帮到大家,Progress together

评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

肥仔哥哥1930

来一波支持,吃不了亏上不了当

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值