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

http://coderj.org/blog/2011/08/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,实例如下:
  
  
  
  
  1. import java.beans.BeanInfo; 
  2. import java.beans.Introspector; 
  3. import java.beans.IntrospectionException; 
  4. import java.beans.PropertyDescriptor; 
  5. public class SimpleBean{ 
  6.     private final String name = "SimpleBean"
  7.     private int size; 
  8.     public String getName(){ 
  9.         return this.name; 
  10.     } 
  11.     public int getSize(){ 
  12.             return this.size; 
  13.     } 
  14.     public void setSize( int size ) { 
  15.         this.size = size; 
  16.     } 
  17.     public static void main( String[] args )            throws IntrospectionException   { 
  18.         BeanInfo info = Introspector.getBeanInfo( SimpleBean.class ); 
  19. 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代码如下:
  
  
  
  
  1. public Object execute(Object o, InternalContextAdapter context) 
  2.     throws MethodInvocationException 
  3.   
  4.     if (referenceType == RUNT) 
  5.         return null
  6.     Object result = getVariableValue(context, rootString); 
  7.   
  8.     if (result == null && !strictRef) 
  9.     { 
  10.         return EventHandlerUtil.invalidGetMethod(rsvc, context, 
  11.                 "$" + rootString, nullnull, uberInfo); 
  12.     } 
  13.   
  14.     try 
  15.     { 
  16.         Object previousResult = result; 
  17.         int failedChild = -1
  18.         for (int i = 0; i < numChildren; i++) 
  19.         { 
  20.             if (strictRef && result == null
  21.             { 
  22.                 String name = jjtGetChild(i).getFirstToken().image; 
  23.                 throw new VelocityException("Attempted to access '" 
  24.                     + name + "' on a null value at " 
  25.                     + Log.formatFileString(uberInfo.getTemplateName(), 
  26.                     + jjtGetChild(i).getLine(), jjtGetChild(i).getColumn())); 
  27.             } 
  28.             previousResult = result; 
  29.             //遍历执行子节点的execute方法 
  30.             result = jjtGetChild(i).execute(result,context); 
  31.             if (result == null && !strictRef)  // If strict and null then well catch this 
  32.                                                // next time through the loop 
  33.             { 
  34.                 failedChild = i; 
  35.                 break
  36.             } 
  37.         } 
  38.   
  39.         /** 
  40.         ...... 
  41.         */ 
 
1.execute方法先根据对象的名字从context中获取对象实例。
2.遍历所有子节点,执行子节点的execute方法。
2.2.1 ASTMethod节点渲染
ASTMethod的execute方法中关键代码如下:
  
  
  
  
  1. public Object execute(Object o, InternalContextAdapter context) 
  2.     throws MethodInvocationException 
  3.     if (o instanceof NullInstance && ((NullInstance) o).isNotNull()) { 
  4.         return o; 
  5.     } 
  6.   
  7.     /* 
  8.      *  获取方法信息 
  9.      */ 
  10.   
  11.     VelMethod method = null
  12.   
  13.     Object [] params = new Object[paramCount]; 
  14.   
  15.     try 
  16.     { 
  17.         // 计算参数类型 
  18.         final Class[] paramClasses = paramCount > 0 ? new Class[paramCount] : ArrayUtils.EMPTY_CLASS_ARRAY; 
  19.   
  20.         for (int j = 0; j < paramCount; j++) 
  21.         { 
  22.             params[j] = jjtGetChild(j + 1).value(context); 
  23.   
  24.             if (params[j] != null
  25.             { 
  26.                 paramClasses[j] = params[j].getClass(); 
  27.             } 
  28.         } 
  29.   
  30.          //从cache中获取Method信息 
  31.         MethodCacheKey mck = new MethodCacheKey(methodName, paramClasses); 
  32.         IntrospectionCacheData icd =  context.icacheGet( mck ); 
  33.   
  34.         if ( icd != null && (o != null && icd.contextData == o.getClass()) ) 
  35.         { 
  36.             method = (VelMethod) icd.thingy; 
  37.         } 
  38.         else 
  39.         { 
  40.             //缓存未命中,调用UberIntrospectImpl.getMethod()执行自省 
  41.             method = rsvc.getUberspect().getMethod(o, methodName, params, new Info(getTemplateName(), getLine(), getColumn())); 
  42.   
  43.             if ((method != null) && (o != null)) 
  44.             { 
  45.                 icd = new IntrospectionCacheData(); 
  46.                 icd.contextData = o.getClass(); 
  47.                 icd.thingy = method; 
  48.                 //更新缓存 
  49.                 context.icachePut( mck, icd ); 
  50.             } 
  51.         } 
  52.   
  53.         if (typeOptimum && method instanceof VelMethodImpl) { 
  54.             this.recordedData = icd; 
  55.         } 
  56.   
  57.         /* 
  58.          *  .... 
  59.          */ 
 
1.首先从IntrospectionCache中查找已经缓存的自省结果信息
2.如果未找到,则使用uberspector进行自省,获取方法信息,并缓存自省结果。
3.调用自省返回的VelMethod的invoke方法,获取执行结果。
其中,获取方法信息的过程
method = rsvc.getUberspect().getMethod(o, methodName, params, new Info(getTemplateName(), getLine(), getColumn()));
实际调用就是UberspectImpl.getMethod()方法,该方法执行流程如下:
  
  
  
  
  1. public VelMethod getMethod(Object obj, String methodName, Object[] args, Info i) 
  2.         throws Exception 
  3.     if (obj == null
  4.     { 
  5.         return null
  6.     } 
  7.     //调用Inspector.getMethod() 
  8.     Method m = introspector.getMethod(obj.getClass(), methodName, args); 
  9.     if (m != null
  10.     { //封装VelMethodImpl 
  11.         return new VelMethodImpl(m); 
  12.     } 
  13.   
  14.     Class cls = obj.getClass(); 
  15.     // if it's an array 
  16.     if (cls.isArray()) 
  17.     { 
  18.         // check for support via our array->list wrapper 
  19.         m = introspector.getMethod(ArrayListWrapper.class, methodName, args); 
  20.         if (m != null
  21.         { 
  22.             // and create a method that knows to wrap the value 
  23.             // before invoking the method 
  24.             return new VelMethodImpl(m, true); 
  25.         } 
  26.     } 
  27.     // watch for classes, to allow calling their static methods (VELOCITY-102) 
  28.     else if (cls == Class.class
  29.     { 
  30.         m = introspector.getMethod((Class)obj, methodName, args); 
  31.         if (m != null
  32.         { 
  33.             return new VelMethodImpl(m); 
  34.         } 
  35.     } 
  36.     return null
 
该方式实际调用Introspector.getMethod()方法。
  
  
  
  
  1. public Method getMethod(final Class c, final String name, final Object[] params) 
  2.     throws IllegalArgumentException 
  3.     try 
  4.     { 
  5.         //调用父类IntrospectorBase.getMethod()方法 
  6.         return super.getMethod(c, name, params); 
  7.     } 
  8.     catch(MethodMap.AmbiguousException ae) 
  9.     { 
  10.        /*异常处理*/ 
  11.     } 
  12.   
  13.     return null
 
Introspector.getMethod()实际只是扩展了其父类的getMethod方法,增加了异常日志功能。
IntrospectorBase.getMethod()代码如下:
  
  
  
  
  1. public Method getMethod(final Class c, final String name, final Object[] params) 
  2.         throws IllegalArgumentException,MethodMap.AmbiguousException 
  3.     if (c == null
  4.     { 
  5.         throw new IllegalArgumentException ("class object is null!"); 
  6.     } 
  7.   
  8.     if (params == null
  9.     { 
  10.         throw new IllegalArgumentException("params object is null!"); 
  11.     } 
  12.   
  13.     IntrospectorCache ic = getIntrospectorCache(); 
  14.   
  15.     ClassMap classMap = ic.get(c); 
  16.     if (classMap == null
  17.     { 
  18.         classMap = ic.put(c); 
  19.     } 
  20.   
  21.     return classMap.findMethod(name, params); 
 
该方法首先获取从IntrospectorCache中获取表示类信息的classMap,如果没找到则在cache中put该类型信息。有意思的是这里没有常见的缓存未命中直接查询的过程,而是直接更新缓存,也就意味着put方法里有构造类型信息的过程。
IntrospectorCache.put()代码如下
.......

你可能感兴趣的:(职场,velocity,休闲)