原文地址: http://www.khotyn.com/2011/07/22/velocity_sourcecode/
很久之前就接触了Velocity,但是一直只会根据它的模板语法写一些模板,非常惭愧,于是最近看了一下Velocity的源代码,在这里记录一下看了以后的一点心得体会。
对于Velocity是什么东西,我相信这个不用多说,在说Velocity的源代码之前,先看一下几个看Velocity的源代码之前必须需要了解的东西:
说到Velocity,不得不谈一下JavaCC,JavaCC是一个用于生成解析器的工具,它可以将一份语法定义(以.jj为后缀的文件)转化成Java代码用于检查一本文本是否符合这一份语法定义。更加具体的信息大家可以查看JavaCC的官网或者看一看这一篇文章。
JJTree是JavaCC提供的一个工具,JJTree可以将一份语法定义(以.jjt为后缀的文件,语法和.jj文件基本相同)转化成Java 代码,这段代码可以检查一份输入是否符合这一份语法定义,并且最后还会生成一颗抽象语法树提供给使用来遍历。更多关于JJTree的内容大家可以看一看这 一篇文章。Velocity就将其模板语法定义成了一个jjt文件,然后根据这一份jjt文件生成了velocity模板的解析器。
前面我们已经提到,JJTree工具生成的解析器可以将模板文件解析成一个抽象语法树,这里简单解释一下抽象语法树,抽象语法树是一种表示源代码的 抽象语法结构的方法,这样解释起来可能比较晦涩,我们用例子来看看,假设我们定义了一个加减乘除的语法,其语法和数学上的是一样的,现在有下面一个表达 式:
1 + 2 * 3最后得到了一棵AST就是这样的:
有了这样一颗AST,整个表达式的求值就可以通过递归的方式来非常快速的进行。
Velocity的模板解析过程是由一个输入(可以是一个模板文件,或者是就是一个String或者InputStream)得到一棵抽象语法树的过程。
首先,Velocity为其模板语法定义了一份jjt文件,根据这一份jjt文件,使用JJTree生成了一个解析器。
然后,Velocity将模板解析的过程完全交给了解析器,调用解析器的parse方法直接得到一棵AST,这一棵AST的每一个节点都对应一个 SimpleNode的子类,其中不同的语法元素对应的不同的SimpleNode,比如#if条件表达式对应的SimpleNode是 ASTIfStatement,而#stop指令对应的SimpleNode是ASTStop。
得到了AST以后,模板的渲染就比较简单了,无非就是递归地调用各个节点的SimpleNode的render方法来完成模板的渲染过程,相当简单。
在了解了模板的渲染过程以后,最想了解的便是模板上的方法调用是怎么在渲染过程中执行的,首先来说明一下对于Velocity里面的引用,比 如$foo这样的,最后都被解析成AST中的ASTReference节点,而对于$foo.name这样的,ASTReference下面有一个 ASTIdentifier节点,$foo.saySomething()这样的,ASTReference下面有一个ASTMethod节点。不论是 ASTMethod还是ASTIdentifier,最后都是通过Uberspect和Introspector这两个类来完成对方法的查找(关于这两个 类的类名,可以见我的另一篇文章 ),最后调用各种Executor来完成对方法的调用。
Uberspect这个类的功能是通过反射(Reflection)和内省(Introspection)来完成对需要调用的方法的获取的,而 Introspector这个类的功能是根据方法名和方法参数在一个类里面寻找Method对象的。另外,为了提高性能,这里面还对Method的数据进 行了缓存(见IntrospectionCacheData,IntrospectorCache和IntrospectorCacheImpl三个 类),以便下次快速可以找到。
找到Method以后,具体的方法的执行由各个Executor执行,每一个Executor都继承了AbstractExecutor,以此来给外部提供统一的接口去调用。
最后我画了一个Velocity模板解析与渲染部分的整体架构来说明前面的整个过程: