Velocity源码分析(二)――渲染执行之Introspection
2011-10-19 22:57
465 查看
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,实例如下:
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中定义了渲染执行时所需的主要接口。该接口主要提供四个方法:
getIterator():支持迭代#foreache
getMethod():支持方法调用
getPropertyGet():支持获取属性值
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.execute方法先根据对象的名字从context中获取对象实例。
2.遍历所有子节点,执行子节点的execute方法。
2.2.1 ASTMethod节点渲染
ASTMethod的execute方法中关键代码如下:
1.首先从IntrospectionCache中查找已经缓存的自省结果信息
2.如果未找到,则使用uberspector进行自省,获取方法信息,并缓存自省结果。
3.调用自省返回的VelMethod的invoke方法,获取执行结果。
其中,获取方法信息的过程
method = rsvc.getUberspect().getMethod(o, methodName, params, new Info(getTemplateName(), getLine(), getColumn()));
实际调用就是UberspectImpl.getMethod()方法,该方法执行流程如下:
该方式实际调用Introspector.getMethod()方法。
Introspector.getMethod()实际只是扩展了其父类的getMethod方法,增加了异常日志功能。
IntrospectorBase.getMethod()代码如下:
该方法首先获取从IntrospectorCache中获取表示类信息的classMap,如果没找到则在cache中put该类型信息。有意思的是这里没有常见的缓存未命中直接查询的过程,而是直接更新缓存,也就意味着put方法里有构造类型信息的过程。
IntrospectorCache.put()代码如下
.......
一、何为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中定义了渲染执行时所需的主要接口。该接口主要提供四个方法:
getIterator():支持迭代#foreache
getMethod():支持方法调用
getPropertyGet():支持获取属性值
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()代码如下
.......
相关文章推荐
- SpringMVC执行图解和源码分析
- tomcat源码分析-http请求在Container中的执行路线
- Weex Android SDK源码分析之界面渲染(下)
- 查看源码分析activity执行setContentView的流程
- MyBatis源码分析-SQL语句执行的完整流程
- 数据库分库分表中间件 Sharding-JDBC 源码分析 —— SQL 执行
- springMVC源码分析--异常处理机制HandlerExceptionResolver执行原理(二)
- Fabric源码分析之invoke执行流程及block生成分析
- React-Native-源码分析二-JSX如何渲染成原生页面(上)
- jQuery源码分析之ajaxTransport和ajaxPrefilters执行函数之inspectPrefiltersOrTransports
- 深入理解Glide源码,分析之路(二):Glide的执行流程,史上最详细、易懂
- Zookeeper源码分析(3)- Leader执行流程
- Python源码分析2 - 一个简单的Python程序的执行
- 第二人生的源码分析(五十六)OpenGL离屏渲染的实现
- memcached源码分析(一): memcached.c主函数分析 执行流程
- Tor源码分析五 -- 客户端执行流程(libevent调度)
- Celery源码分析(一)-------------从命令执行到生成Worker
- Netty5源码分析(七) -- 异步执行Future和Promise
- Elastic-Job项目源码分析2--console执行一窥
- 天天记录 - 从源码分析ScrollBy和ScrollTo仅执行onDraw方法