高性能EL——Fel探秘,兼谈EL

简介:
   Fel是最近javaeye比较火的关键词,这是由网友 lotusyu开发的一个高性能的EL,从作者给出的数据来看,性能非常优异,跟前段时间温少开源的 Simple EL有的一拼。首先要说,这是个好现象,国内的开源项目越来越多,可以看出开发者的水平是越来越高了,比如我最近还看到有人开源的类似kestel的轻量级MQ—— fqueue也非常不错,有兴趣可以看下我的分析《 fqueue初步分析》。

    进入正文,本文是尝试分析下Fel的实现原理,以及优缺点和aviator——我自己开源的EL之间的简单比较。

    Fel的实现原理跟Simple EL是类似,都是使用template生成中间代码——也就是普通的java代码,然后利用javac编译成class,最后运行,当然,这个过程都是动 态的。JDK6已经引入了编译API,在此之前的版本可以调用sun的类来编译,因为javac其实就是用java实现的。回到Fel里 面,FelCompiler15就是用 com.sun.tools.javac.Main来编译,而FelCompiler16用标准的javax.tools.JavaCompiler来编译的。

    文法和语法解释这块是使用antlr这个parse generator生成的,这块不多说,有兴趣可以看下antlr,整体一个运行的过程是这样:

    expression string  ->  antlr  ->  AST  ->  comiple  ->  java source template  ->  java  class   ->  Expression 

    这个思路我在实现aviator之前就想过,但是后来考虑到API需要用的sun独有的类,而且要求classpath必须有tools.jar这个依赖包,就放弃了这个思路,还是采用ASM生成字节码的方式。题外,velocity的优化可以采用这个思路,我们有这么一个项目是这么做的,也准备开源了。

 

    看看Fel生成的中间代码,例如a+b这样的一个简单的表达式,假设我一开始不知道a和b的类型,编译是这样:

    FelEngine fel  =   new  FelEngineImpl();  
    Expression exp 
=   fel.compile( " a+b " null ); 

    我稍微改了下FEL的源码,让它打印中间生成的java代码,a+b生成的中间结果为:

     package  com.greenpineyu.fel.compile;  
      
    
import  com.greenpineyu.fel.common.NumberUtil;  
    
import  com.greenpineyu.fel.Expression;  
    
import  com.greenpineyu.fel.context.FelContext;  
    
import  org.apache.commons.lang.ObjectUtils;  
    
import  org.apache.commons.lang.StringUtils;  
      
    
public   class  Fel_0   implements  Expression{  
      
        
public  Object eval(FelContext context) {  
            java.lang.Object var_1 
=  (java.lang.Object)context.get( " b " );    // b  
            java.lang.Object var_0  =  (java.lang.Object)context.get( " a " );    // a  
             return  (ObjectUtils.toString(var_0)) + (ObjectUtils.toString(var_1));  
        }  
    } 

     可见,FEL对表达式解析和解释后,利用template生成这么一个普通的java类,而a和b都从context中获取并转化为Object类型,这里没有做任何判断就直接认为a和b是要做字符串相加,然后拼接字符串并返回。

 

     问题出来了,因为没有在编译的时候传入context(我们这里是null),FEL会将a和b的类型默认都为java.lang.Object,a+b解释为字符串拼接。但是运行的时候,我完全可以传入a和b都为数字,那么结果就非常诡异了:

     FelEngine fel  =   new  FelEngineImpl();  
      
    Expression exp 
=  fel.compile( " a+b " null );  
    Map
< String, Object >  env = new  HashMap < String, Object > ();  
    env.put(
" a " 1 );  
    env.put(
" b " 3.14 );  
    System.out.println(exp.eval(
new  MapContext(env))); 

输出:

     13.14  

    1+3.14的结果,作为字符串拼接就是13.14,而不是我们想要的4.14。如果将表达式换成a*b,就完全运行不了

    com.greenpineyu.fel.exception.CompileException:  package  com.greenpineyu.fel.compile;  
      
    
import  com.greenpineyu.fel.common.NumberUtil;  
    
import  com.greenpineyu.fel.Expression;  
    
import  com.greenpineyu.fel.context.FelContext;  
    
import  org.apache.commons.lang.ObjectUtils;  
    
import  org.apache.commons.lang.StringUtils;  
      
    
public   class  Fel_0   implements  Expression{  
      
        
public  Object eval(FelContext context) {  
            java.lang.Object var_1 
=  (java.lang.Object)context.get( " b " );    // b  
            java.lang.Object var_0  =  (java.lang.Object)context.get( " a " );    // a  
             return  (var_0) * (var_1);  
        }  
    }  
      
    [Fel_0.java:
14 : 运算符  *  不能应用于 java.lang.Object,java.lang.Object]  
        at com.greenpineyu.fel.compile.FelCompiler16.compileToClass(FelCompiler16.java:
113 )  
        at com.greenpineyu.fel.compile.FelCompiler16.compile(FelCompiler16.java:
87 )  
        at com.greenpineyu.fel.compile.CompileService.compile(CompileService.java:
66 )  
        at com.greenpineyu.fel.FelEngineImpl.compile(FelEngineImpl.java:
62 )  
        at TEst.main(TEst.java:
14 )  
    Exception in thread 
" main "  java.lang.NullPointerException  
        at TEst.main(TEst.java:
18

 

    这个问题对于Simple EL同样存在,如果没有在编译的时候能确定变量类型,这无法生成正确的中间代码,导致运行时出错,并且有可能造成非常诡异的bug。

 

    这个问题的本质是因为Fel和Simple EL没有自己的类型系统,他们都是直接使用java的类型的系统,并且必须在编译的时候确定变量类型,才能生成高效和正确的代码,我们可以将它们称为“强类型的EL“。

 

    现在让我们在编译的时候给a和b加上类型,看看生成的中间代码:

    FelEngine fel  =   new  FelEngineImpl();  
    fel.getContext().set(
" a " 1 );  
    fel.getContext().set(
" b " 3.14 );  
    Expression exp 
=  fel.compile( " a+b " null );  
    Map
< String, Object >  env  =   new  HashMap < String, Object > ();  
    env.put(
" a " 1 );  
    env.put(
" b " 3.14 );  
    System.out.println(exp.eval(
new  MapContext(env))); 

    查看中间代码:

     package  com.greenpineyu.fel.compile;  
      
    
import  com.greenpineyu.fel.common.NumberUtil;  
    
import  com.greenpineyu.fel.Expression;  
    
import  com.greenpineyu.fel.context.FelContext;  
    
import  org.apache.commons.lang.ObjectUtils;  
    
import  org.apache.commons.lang.StringUtils;  
      
    
public   class  Fel_0   implements  Expression{  
      
        
public  Object eval(FelContext context) {  
            
double  var_1  =  ((java.lang.Number)context.get( " b " )).doubleValue();    // b  
             double  var_0  =  ((java.lang.Number)context.get( " a " )).doubleValue();    // a  
             return  (var_0) + (var_1);  
        }  
    } 

可以看到这次将a和b都强制转为double类型了,做数值相加,结果也正确了:

     4.140000000000001  

    Simple EL我没看过代码,这里猜测它的实现也应该是类似的,也应该有同样的问题。

    相比来说,aviator这是一个弱类型的EL在编译的时候不对变量类型做任何假设,而是在运行时做类型判断和自动转化。过去提过,我给aviator的定位是一个介于EL和script之间的东西,它有自己的类型系统。 例如,3这个数字,在java里可能是long,int,short,byte,而aviator统一为AviatorLong这个类型。为了在这两个类 型之间做适配,就需要做很多的判断和box,unbox操作。这些判断和转化都是运行时进行的,因此aviator没有办法做到Fel这样的高效,但是已 经做到至少跟groovy这样的弱类型脚本语言一个级别,也超过了JXEL这样的纯解释EL,具体可以看这个性能测试

 

   强类型还是弱类型,这是一个选择问题,如果你能在运行前就确定变量的类型,那么使用Fel应该可以达到或者接近于原生java执行的效率,但是失去了灵活性;如果你无法确定变量类型,则只能采用弱类型的EL。

 

   EL涌现的越来越多,这个现象有点类似消息中间件领域,越来越多面向特定领域的轻量级MQ的出现,而不是原来那种大而笨重的通用MQ大行其道,一方面是互 联网应用的发展,需求不是通用系统能够满足的,另一方面我认为也是开发者素质的提高,大家都能造适合自己的轮子。从EL这方面来说,我也认为会有越来越多 特定于领域的,优点和缺点一样鲜明的EL出现,它们包含设计者自己的目标和口味,选择很多,就看取舍。

文章转自庄周梦蝶  ,原文发布时间2011-09-17

目录
相关文章
|
算法 数据可视化 前端开发
第三代软件开发-自定义Button
欢迎来到我们的 QML & C++ 项目!这个项目结合了 QML(Qt Meta-Object Language)和 C++ 的强大功能,旨在开发出色的用户界面和高性能的后端逻辑。 在项目中,我们利用 QML 的声明式语法和可视化设计能力创建出现代化的用户界面。通过直观的编码和可重用的组件,我们能够迅速开发出丰富多样的界面效果和动画效果。同时,我们利用 QML 强大的集成能力,轻松将 C++ 的底层逻辑和数据模型集成到前端界面中。 在后端方面,我们使用 C++ 编写高性能的算法、数据处理和计算逻辑。C++ 是一种强大的编程语言,能够提供卓越的性能和可扩展性。我们的团队致力于优化代码,减少资
|
SQL 前端开发 算法
【HR专用】Vue+SpringBoot,实现人才招聘库的开发(后端部分)(一)
【HR专用】Vue+SpringBoot,实现人才招聘库的开发(后端部分)
153 0
|
前端开发 JavaScript 小程序
7 款最棒的开源 React UI 库测评 - 特别针对国内使用场景推荐
优秀的 React UI 组件库,帮我们节省开发时间,提高开发效率,统一设计语言。更棒的是内置的功能复杂,我们自己很难处理的常用组件,比如表格、表单、富文本编辑器、时间日期选择器、实时拖拽组件等,再进一步,还有帮我们把组件的轮子装好的 React admin 后台管理系统。本文推荐 7 款适用于中文使用者习惯的开源 React UI 库,特别针对国内使用场景推荐。
1042 0
|
5月前
一文搞懂:关于VueElement组件el
一文搞懂:关于VueElement组件el
25 0
|
6月前
|
JavaScript Java 测试技术
基于SpringBoot+Vue的卓越导师双选系统的设计与实现(源码+lw+部署文档+讲解等)
基于SpringBoot+Vue的卓越导师双选系统的设计与实现(源码+lw+部署文档+讲解等)
|
6月前
|
测试技术 API 开发者
从零开始学习 form-data:轻松上手数据交互技术
在 Web 开发和 API 设计中,表单数据的传输是一项基本的需求。本文着重介绍form-data—一种广泛应用于数据传输的编码方法。
|
算法 数据可视化 前端开发
第三代软件开发-自定义Slider(一)
欢迎来到我们的 QML & C++ 项目!这个项目结合了 QML(Qt Meta-Object Language)和 C++ 的强大功能,旨在开发出色的用户界面和高性能的后端逻辑。 在项目中,我们利用 QML 的声明式语法和可视化设计能力创建出现代化的用户界面。通过直观的编码和可重用的组件,我们能够迅速开发出丰富多样的界面效果和动画效果。同时,我们利用 QML 强大的集成能力,轻松将 C++ 的底层逻辑和数据模型集成到前端界面中。 在后端方面,我们使用 C++ 编写高性能的算法、数据处理和计算逻辑。C++ 是一种强大的编程语言,能够提供卓越的性能和可扩展性。我们的团队致力于优化代码,减少资
|
前端开发 数据库
【HR专用】Vue+SpringBoot,实现人才招聘库的开发(后端部分)(二)
【HR专用】Vue+SpringBoot,实现人才招聘库的开发(后端部分)
让el-form更好用,通过配置的方式
让el-form更好用,通过配置的方式
427 0