Posts tagged Velocity

一个velocity.log引发的问题

2

最近在项目中采用了velocity最一些简单的规则做动态解析,大概的代码如下(经过一定程度简化):

image

这段代码在开发环境下运行地一直很正常,直到昨天部署到了测试环境了,引发了一个诡异的NullPointException

原因分析

首先经过一位同事的Debug,发现在做evaluate,把模板解析成NodeTree,获取parser的过程中,parserPool的值是null,从而引发了NullPointException,代码如下:

image

正常情况下,velocity初始化之后,parserPool都应该正常初始化了,不应该出现parserPoolnull的情况,只有一种可能是,parserPool初始化失败了。

继续跟踪velocity初始化的代码:

image

还没有debug到initializeParserPool()这一步,在initializeLog()这一步velocity就抛出了异常:

FileNotFoundException: velocity.log (Permission denied)

那么分析一下velocity日志初始化的过程:

  1. velocity的日志系统的初始化都在LogManager.createLogChute()这个方法里面
  2. 首先,在velocity的jar里面的velocity.properties,配置了一个
    runtime.log.logsystem.class = org.apache.velocity.runtime.log.AvalonLogChute,
    org.apache.velocity.runtime.log.Log4JLogChute,
    org.apache.velocity.runtime.log.CommonsLogLogChute,
    org.apache.velocity.runtime.log.ServletLogChute,
    org.apache.velocity.runtime.log.JdkLogChute
    

    velocity初始化的时候,会遍历这个列表,一个个尝试,而在我们的项目中,有log4j,所以采用的就是第二个org.apache.velocity.runtime.log.Log4JLogChute

  3. 在初始化org.apache.velocity.runtime.log.Log4JLogChute的过程中,velocity会再次去velocity.properties里面找runtime.log = velocity.log,就是运行时日志的路径,而log4j创建appender的时候,就直接采用new File("velocity.log")的方式在文件系统中创建一个日志文件。
  4. 大家知道new File("velocity.log")这种方式是在usr.dir下面直接创建文件的,由于我们的应用是jetty,usr.dir指向的目录是JETTY_HOME

那么,问题的原因应该大概清楚了:

  • 在测试环境上jetty安装的目录是在/usr下面的,普通的用户是对/usr目录没有写权限的,所以出现了Permission Denied
  • 但是对于本机开发环境,尽管jetty也是安装在/usr下面,但是由于/usr目录的权限已经被手工的设置为了任何人都能访问,所以没有出现了Permission Deinied的情况。

问题解决

只需要关闭掉velocity的运行时日志,在ve.init()前加上以下一段代码即可:

ve.setProperty(RuntimeConstants.RUNTIME_LOG_LOGSYSTEM, new NullLogChute());

总结

大家以后再使用velocity时候,需要特别注意下velocity.log的这个问题,我到Google上搜索了一把,其实这个问题还是挺普遍的,包括之前一位同事在去年做jetty迁移的时候也遇到了同样的问题,总之,以后使用的时候小心吧。

Velocity源码浅析

3

很久之前就接触了Velocity,但是一直只会根据它的模板语法写一些模板,非常惭愧,于是最近看了一下Velocity的源代码,在这里记录一下看了以后的一点心得体会。

一、基本概念

对于Velocity是什么东西,我相信这个不用多说,在说Velocity的源代码之前,先看一下几个看Velocity的源代码之前必须需要了解的东西:

JavaCC和JJTree

说到Velocity,不得不谈一下JavaCC,JavaCC是一个用于生成解析器的工具,它可以将一份语法定义(以.jj为后缀的文件)转化成Java代码用于检查一本文本是否符合这一份语法定义。更加具体的信息大家可以查看JavaCC的官网或者看一看这一篇文章。

JJTree是JavaCC提供的一个工具,JJTree可以将一份语法定义(以.jjt为后缀的文件,语法和.jj文件基本相同)转化成Java代码,这段代码可以检查一份输入是否符合这一份语法定义,并且最后还会生成一颗抽象语法树提供给使用来遍历。更多关于JJTree的内容大家可以看一看这一篇文章。Velocity就将其模板语法定义成了一个jjt文件,然后根据这一份jjt文件生成了velocity模板的解析器。

抽象语法树(Abstract Syntax Tree)

前面我们已经提到,JJTree工具生成的解析器可以将模板文件解析成一个抽象语法树,这里简单解释一下抽象语法树,抽象语法树是一种表示源代码的抽象语法结构的方法,这样解释起来可能比较晦涩,我们用例子来看看,假设我们定义了一个加减乘除的语法,其语法和数学上的是一样的,现在有下面一个表达式:

1 + 2 * 3

最后得到了一棵AST就是这样的:

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模板解析与渲染部分的整体架构来说明前面的整个过程:

VelocityTemplateParsingRendering

五、相关资料

乱谈Introspection

2

最近在看Apache Velocity的源代码,里面有一个类名叫做Introspector,我一直不理解为什么取这个名字,今天特地去查了一下。

Introspection在精神哲学里面表示的是一种对自我的情绪,精神状态观察、学习的一种方法。而在计算机科学中,Type Introspection是指OOP语言在运行时推断对象类型的一种特性,这个特性对于Java而言,就是在运行时可以拿到一个对象的Class,而我理解对象的Class应该是作为程序本身的一部分的,所以Introspection实际上是对程序本身的一种“观察”。再来理解下Velocity里面的Introspector这个类,其主要功能是根据方法名和参数拿到具体的Method对象,在oracle的JDK文档中,也提到了Introspection是自动分析Bean以获取Bean的属性,方法等等信息的过程,这里也可以看作是对程序本身的一个观察,分析过程。

从上面的几个地方来看,Introspection的含义都包含了对自我的观察和分析过程,那么Velocity的Introspector类取这样一个名字也可以理解了,其实就是获取程序本身信息的一个类。

而Velocity的另一个类Uberspect的前缀Uber是极大的意思,根据这个类的注释说Uberspect是“introspector”和”reflection”的一个“联合”,功能上还是“内省”的功能,只不过因为联合了“introspector”和“reflection”,所以换上了一个Uber的前缀。

Go to Top