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

原文首发自个人博客, http://jiangbo.me/blog/2011/08/26/velocity_introspection/ 此处同步转载
一、何为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,实例如下:
Introspector.getBeanInfo(SimpleBean.class)是Java提供的一个自省工具类,可以在运行时获取SimpleBean类的类型信息BeanInfo,包括属性名、方法名、Bean描述等等信息。
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() );
}
}
 
查阅资料过程中发现有些人认为自省即反射(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

 

你可能感兴趣的:(velocity源码分析)