Velocity源码分析(二)——渲染执行之Introspection

26 August 2011

一、何为Introspection
Instrospection(自省,xing,“吾日三省吾身”的“省”)源自哲学术语,指的是一种自我检视的精神行为。
Introspection is the self-observation and reporting of conscious inner thoughts, desires and sensations. It is a conscious and purposive process relying on thinking, reasoning, and examining one's own thoughts, feelings, and, in more spiritual cases, one's soul. 
——Wikipedia
在计算机科学中,借用了哲学中的Introspeciton术语,表示一种能够识别一个事物它是什么,知道什么,能做什么的能力。典型的应用场景是面向对象语言中的类型自省(type introspeciton)。
In computing, type introspection is a capability of some object-oriented programming languages to determine the type of an object at runtime.
——Wikipedia
以Java为例,Java提供了可以在运行时获取和检查JavaBean的接口API,实例如下:
import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
public class SimpleBean{
    private final String name = "SimpleBean";
    private int size;
    public String getName(){
        return this.name;
    }
    public int getSize(){
            return this.size;
    }
    public void setSize( int size ) {
        this.size = size;
    }
    public static void main( String[] args )            throws IntrospectionException   {
        BeanInfo info = Introspector.getBeanInfo( SimpleBean.class );
for ( PropertyDescriptor pd : info.getPropertyDescriptors() )             System.out.println( pd.getName() );
}
}
Introspector.getBeanInfo(SimpleBean.class)是Java提供的一个自省工具类,可以在运行时获取SimpleBean类的类型信息BeanInfo,包括属性名、方法名、Bean描述等等信息。
查阅资料过程中发现有些人认为自省即反射(Reflection),反射即自省,因为Java中自省是通过反射实现的。我认为这两个概念还是有区别的,自省是一个目的或者说机制,是一个上层的接口封装,而反射是达到这个目的或者实现这个机制的方法,是底层的具体实现。
二、Velocity中的渲染执行
2.1 velocity中Introspection概述
Velocity作为一种模板语言允许我们向Context中放置一些JavaBean实例,并在模板中通过变量方式引用。如下所示:
Welcome! ${person.name} !
该模板中有一个引用变量${person.name},在执行渲染时必须要知道person是个什么东东,person.name又是个什么东东,这里就需要自省机制发挥作用。
Veloctiy的的自省机制实现位于源码包org.apache.velocity.util.introspection中,其主要类图结构如下:
Uberspect中定义了渲染执行时所需的主要接口。该接口主要提供四个方法:
  1. getIterator():支持迭代#foreache
  2. getMethod():支持方法调用
  3. getPropertyGet():支持获取属性值
  4. getPropertySet():支持设置属性值
Uberspect有个默认的实现UberspectImpl,该实现使用默认的Introspector完成基本的自省功能。Introspector扩展自基类IntrospectorBase,增添异常日志功能。

IntrospectorBase内部维护了一个introspectCache,用于缓存已经完成自省的类和方法信息。
IntrospectorCacheImpl内通过一个HashMap维护一个class与其对应的类型信息,类型信息用一个ClassMap表示。
一个ClassMap内部维护了一个MethodCache,用于缓存该类已经解析出得方法信息。
MethodMap表示一个方法信息。
2.2 渲染执行详细流程
下面一如下模板为例,解释velocity中introspection的实际执行:
template.vm
${person.sayHi()}! I'm ${person.name}
该模板的作用表示分别调用context中名为person的对象的sayHi()方法和name属性。该模板经过语法解析生成的AST如下(关于AST解析请参考上一篇velocity源码分析):
图1.语法解析后的AST
${person.say()}被解析为一个拥有AST子节点的ASTReference节点,”! I’m”为一个ASTText节点,$person.name被解析为一个拥有ASTIdentifier子节点的ASTReference节点,”。”被解析为一个ASTText节点。
引擎从根节点开始执行渲染ASTprocess的render方法主要是遍历子节点,依次执行子节点的渲染方法。
ASTReference.render()方法主要调用其内部的execute()方法获取实际的引用值,execute代码如下:
    public Object execute(Object o, InternalContextAdapter context)
        throws MethodInvocationException
    {

if (referenceType == RUNT)
return null;
Object result = getVariableValue(context, rootString);

if (result == null && !strictRef)
{
return EventHandlerUtil.invalidGetMethod(rsvc, context,
"$" + rootString, null, null, uberInfo);
}

try
{
Object previousResult = result;
int failedChild = -1;
for (int i = 0; i < numChildren; i++)
{
if (strictRef && result == null)
{
String name = jjtGetChild(i).getFirstToken().image;
throw new VelocityException("Attempted to access '"
+ name + "' on a null value at "
+ Log.formatFileString(uberInfo.getTemplateName(),
+ jjtGetChild(i).getLine(), jjtGetChild(i).getColumn()));
}
previousResult = result;
//遍历执行子节点的execute方法
result = jjtGetChild(i).execute(result,context);
if (result == null && !strictRef) // If strict and null then well catch this
// next time through the loop
{
failedChild = i;
break;
}
}

/**
......
*/
}

1.execute方法先根据对象的名字从context中获取对象实例。
2.遍历所有子节点,执行子节点的execute方法。
2.2.1 ASTMethod节点渲染
ASTMethod的execute方法中关键代码如下:
    public Object execute(Object o, InternalContextAdapter context)
        throws MethodInvocationException
    {
        if (o instanceof NullInstance && ((NullInstance) o).isNotNull()) {
            return o;
        }

/*
* 获取方法信息
*/

VelMethod method = null;

Object [] params = new Object[paramCount];

try
{
// 计算参数类型
final Class[] paramClasses = paramCount > 0 ? new Class[paramCount] : ArrayUtils.EMPTY_CLASS_ARRAY;

for (int j = 0; j < paramCount; j++)
{
params[j] = jjtGetChild(j + 1).value(context);

if (params[j] != null)
{
paramClasses[j] = params[j].getClass();
}
}

//从cache中获取Method信息
MethodCacheKey mck = new MethodCacheKey(methodName, paramClasses);
IntrospectionCacheData icd = context.icacheGet( mck );

if ( icd != null && (o != null && icd.contextData == o.getClass()) )
{
method = (VelMethod) icd.thingy;
}
else
{
//缓存未命中,调用UberIntrospectImpl.getMethod()执行自省
method = rsvc.getUberspect().getMethod(o, methodName, params, new Info(getTemplateName(), getLine(), getColumn()));

if ((method != null) && (o != null))
{
icd = new IntrospectionCacheData();
icd.contextData = o.getClass();
icd.thingy = method;
//更新缓存
context.icachePut( mck, icd );
}
}

if (typeOptimum && method instanceof VelMethodImpl) {
this.recordedData = icd;
}

/*
* ....
*/
}

1.首先从IntrospectionCache中查找已经缓存的自省结果信息
2.如果未找到,则使用uberspector进行自省,获取方法信息,并缓存自省结果。
3.调用自省返回的VelMethod的invoke方法,获取执行结果。
其中,获取方法信息的过程
method = rsvc.getUberspect().getMethod(o, methodName, params, new Info(getTemplateName(), getLine(), getColumn()));
实际调用就是UberspectImpl.getMethod()方法,该方法执行流程如下:
    public VelMethod getMethod(Object obj, String methodName, Object[] args, Info i)
            throws Exception
    {
        if (obj == null)
        {
            return null;
        }
        //调用Inspector.getMethod()
        Method m = introspector.getMethod(obj.getClass(), methodName, args);
        if (m != null)
        {
            //封装VelMethodImpl
            return new VelMethodImpl(m);
        }

Class cls = obj.getClass();
// if it's an array
if (cls.isArray())
{
// check for support via our array->list wrapper
m = introspector.getMethod(ArrayListWrapper.class, methodName, args);
if (m != null)
{
// and create a method that knows to wrap the value
// before invoking the method
return new VelMethodImpl(m, true);
}
}
// watch for classes, to allow calling their static methods (VELOCITY-102)
else if (cls == Class.class)
{
m = introspector.getMethod((Class)obj, methodName, args);
if (m != null)
{
return new VelMethodImpl(m);
}
}
return null;
}

该方式实际调用Introspector.getMethod()方法。
    public Method getMethod(final Class c, final String name, final Object[] params)
        throws IllegalArgumentException
    {
        try
        {
            //调用父类IntrospectorBase.getMethod()方法
            return super.getMethod(c, name, params);
        }
        catch(MethodMap.AmbiguousException ae)
        {
           /*异常处理*/
        }

return null;
}

Introspector.getMethod()实际只是扩展了其父类的getMethod方法,增加了异常日志功能。
IntrospectorBase.getMethod()代码如下:
    public Method getMethod(final Class c, final String name, final Object[] params)
            throws IllegalArgumentException,MethodMap.AmbiguousException
    {
        if (c == null)
        {
            throw new IllegalArgumentException ("class object is null!");
        }
        
        if (params == null)
        {
            throw new IllegalArgumentException("params object is null!");
        }

IntrospectorCache ic = getIntrospectorCache();

ClassMap classMap = ic.get(c);
if (classMap == null)
{
classMap = ic.put(c);
}

return classMap.findMethod(name, params);
}

该方法首先获取从IntrospectorCache中获取表示类信息的classMap,如果没找到则在cache中put该类型信息。有意思的是这里没有常见的缓存未命中直接查询的过程,而是直接更新缓存,也就意味着put方法里有构造类型信息的过程。
IntrospectorCache.put()代码如下
    public ClassMap put(final Class c)
    {
        //构造ClassMap
        final ClassMap classMap = new ClassMap(c, log);
        synchronized (classMapCache)
        {
            classMapCache.put(c, classMap);
            classNameCache.add(c.getName());
        }
        return classMap;
    }
put方法首先构造一个ClassMap,然后更新classMapCache。
构造ClassMap的过程如下:
    public ClassMap(final Class clazz, final Log log)
    {
        this.clazz = clazz;
        this.log = log;

if (debugReflection && log.isDebugEnabled())
{
log.debug("=================================================================");
log.debug("== Class: " + clazz);
}

methodCache = createMethodCache();

if (debugReflection && log.isDebugEnabled())
{
log.debug("=================================================================");
}
}

关键是构造一个MethodCache,createMethodCache过程如下:
    private MethodCache createMethodCache()
    {
        MethodCache methodCache = new MethodCache(log);
        for (Class classToReflect = getCachedClass(); classToReflect != null ; classToReflect = classToReflect.getSuperclass())
        {
            if (Modifier.isPublic(classToReflect.getModifiers()))
            {
                populateMethodCacheWith(methodCache, classToReflect);
            }
            Class [] interfaces = classToReflect.getInterfaces();
            for (int i = 0; i < interfaces.length; i++)
            {
                populateMethodCacheWithInterface(methodCache, interfaces[i]);
            }
        }
        // return the already initialized cache
        return methodCache;
    }
createMethodCache()首先构造一个MethodCache实例,然后通过反射获得类型的public方法信息,并递归的获取其实现的接口方法信息。
IntrospectorBase.getMethod()方法获取到该ClassMap后,通过classMap.getMethod()返回一个需要的method,由于多态的存在,一个类会有多个同名方法,所以getMethod()过程中有一个根据参数类型寻找最佳匹配的方法getBestMatch()这里有个循环遍历所有方法,并且比较所有参数类型的过程,而且这个过程在每次模板渲染执行时都会进行,代价很高,因此尽量少在放入模板的类中写多态方法有助提高渲染执行性能。
至此一个ClassMap构造完毕,即一个类的自省过程完成。UberinspectorImpl成功的拿到了需要的方法信息,然后将Method封装VelMethodImpl返回。
ASTMethod节点执行渲染时调用invoke方法实际调用的就是Method.invoke(),获得方法执行结果写入输出流中,完成渲染。
2.2.2 ASTIdentifier节点渲染
ASTIdentifier的execute方法中关键代码如下
(TODO:ASTIndentifier.execute()代码)
1.从IntrospectionCache中查找已经缓存的信息。
2.如果缓存未命中,使用uberspector进行自省,并缓存自省结果
3.调用自省的返回的VelPropertyGet的invoke方法,反射执行起get方法。
UberspectImpl.getPropertyGet()方法关键代码如下:
    public VelPropertyGet getPropertyGet(Object obj, String identifier, Info i)
            throws Exception
    {
        if (obj == null)
        {
            return null;
        }

Class claz = obj.getClass();

// 构造get"属性名"()形式的Executor
AbstractExecutor executor = new PropertyExecutor(log, introspector, claz, identifier);

//构造一个Map形式的Executor
if (!executor.isAlive())
{
executor = new MapGetExecutor(log, claz, identifier);
}

// 构造get("属性名")形式的Executor

if (!executor.isAlive())
{
executor = new GetExecutor(log, introspector, claz, identifier);
}
//构建is"属性名"形式的executor
if (!executor.isAlive())
{
executor = new BooleanPropertyExecutor(log, introspector, claz,
identifier);
}

return (executor.isAlive()) ? new VelGetterImpl(executor) : null;
}

1.首先根据对象的class类型和属性名构造一个get”属性名”的方法的PropertyExecutor。
2.如果未找到get”属性名”的方法,则尝试构造一个Map.key形式的MapGetExecutor。
3.如果也不是Map.key形式,尝试构造一个get(“属性名”)的GetExecutor。
4.如果还没找到,则尝试构造一is”属性名”形式的BooleanPropertyExecutor。
5.最终返回一个封装了Executor的VelGetImpl,如果未找到则返回null。
所有的Executor均扩展自AbstractExecutor,VelGetImpl通过内置一个executor执行方法调用。Executor类图关系如下
Executor实现中除MapGetExecutor外,其余的Executor均通过内置一个introspector实现方法的构建,过程与上述ASTMethod节点渲染过程中introspector.getMethod()一致,只是方法名做了封装,如GetExecutor的方法名为get, PropertyExecutor的方法名为get”属性名”, BooleanPropertyExecutor方法名为is”属性名”,具体构建流程不再赘述。
最终,ASTIndentifier.execute()方法通过UberInspectImpl.getPropertyGet()获得VelGetImpl,并调用VelGetImpl.invoke()获取方法执行的结果,写入输出流,完成渲染。
UberInspectImpl.getPropertySet()的执行过程于getPropertyGet()大体一致,区别在于获取的是VelSetImpl,内置了SetExecutor,SetExecutor有三个扩展对应为MapSetExecutor(Map.key=value形式),PutExecutor(put(key, value)形式),SetPropertyExecutor(set”属性名”(value)形式)。其内部同样使用introspector.getMethod()方法构建Method,通过反射执行设置属性值。
三、总结
总的来说,作为模板语言velocity提供了可用的自省机制完成模板中对象引用及方法执行的的渲染。并在自省过程中提供了有效的缓存机制用以提升自省效率。但每次依旧需要解析AST,反射执行引用节点的方法,效率方面似乎还有优化的余地。
参考文档
《Java Introspeciton》 http://download.oracle.com/javase/tutorial/javabeans/introspection/index.html
《Type Introspection》 http://en.wikipedia.org/wiki/Type_introspection
《Introspection》http://en.wikipedia.org/wiki/Introspection



blog comments powered by Disqus