计算机术语中introspection,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,实例如下:

importjava.beans.BeanInfo;

importjava.beans.Introspector;

importjava.beans.IntrospectionException;

importjava.beans.PropertyDescriptor;

publicclassSimpleBean{

privatefinalString name ="SimpleBean";

privateintsize;

publicString getName(){

returnthis.name;

}

publicintgetSize(){

returnthis.size;

}

publicvoidsetSize(intsize ) {

this.size = size;

}

publicstaticvoidmain( String[] args )throwsIntrospectionException   {

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中,其主要类图结构如下:

%E7%B1%BB%E5%9B%BE%E7%BB%93%E6%9E%84.png

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源码分析):

%E8%AF%AD%E6%B3%95%E6%A0%91.png

图1.语法解析后的AST

${person.say()}被解析为一个拥有AST子节点的ASTReference节点,”! I’m”为一个ASTText节点,$person.name被解析为一个拥有ASTIdentifier子节点的ASTReference节点,”。”被解析为一个ASTText节点。

引擎从根节点开始执行渲染ASTprocess的render方法主要是遍历子节点,依次执行子节点的渲染方法。

ASTReference.render()方法主要调用其内部的execute()方法获取实际的引用值,execute代码如下:

publicObject execute(Object o, InternalContextAdapter context)

throwsMethodInvocationException

{

if(referenceType == RUNT)

returnnull;

Object result = getVariableValue(context, rootString);

if(result ==null&& !strictRef)

{

returnEventHandlerUtil.invalidGetMethod(rsvc, context,

"$"+ rootString,null,null, uberInfo);

}

try

{

Object previousResult = result;

intfailedChild = -1;

for(inti =0; i 

{

if(strictRef && result ==null)

{

String name = jjtGetChild(i).getFirstToken().p_w_picpath;

thrownewVelocityException("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方法中关键代码如下:

publicObject execute(Object o, InternalContextAdapter context)

throwsMethodInvocationException

{

if(oinstanceofNullInstance && ((NullInstance) o).isNotNull()) {

returno;

}

/*

*  获取方法信息

*/

VelMethod method = null;

Object [] params = newObject[paramCount];

try

{

// 计算参数类型

finalClass[] paramClasses = paramCount >0?newClass[paramCount] : ArrayUtils.EMPTY_CLASS_ARRAY;

for(intj =0; j 

{

params[j] = jjtGetChild(j + 1).value(context);

if(params[j] !=null)

{

paramClasses[j] = params[j].getClass();

}

}

//从cache中获取Method信息

MethodCacheKey mck = newMethodCacheKey(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, newInfo(getTemplateName(), getLine(), getColumn()));

if((method !=null) && (o !=null))

{

icd = newIntrospectionCacheData();

icd.contextData = o.getClass();

icd.thingy = method;

//更新缓存

context.icachePut( mck, icd );

}

}

if(typeOptimum && methodinstanceofVelMethodImpl) {

this.recordedData = icd;

}

/*

*  ....

*/

}

1.首先从IntrospectionCache中查找已经缓存的自省结果信息

2.如果未找到,则使用uberspector进行自省,获取方法信息,并缓存自省结果。

3.调用自省返回的VelMethod的invoke方法,获取执行结果。

其中,获取方法信息的过程

method = rsvc.getUberspect().getMethod(o, methodName, params, new Info(getTemplateName(), getLine(), getColumn()));

实际调用就是UberspectImpl.getMethod()方法,该方法执行流程如下:

publicVelMethod getMethod(Object obj, String methodName, Object[] args, Info i)

throwsException

{

if(obj ==null)

{

returnnull;

}

//调用Inspector.getMethod()

Method m = introspector.getMethod(obj.getClass(), methodName, args);

if(m !=null)

{ //封装VelMethodImpl

returnnewVelMethodImpl(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

returnnewVelMethodImpl(m,true);

}

}

// watch for classes, to allow calling their static methods (VELOCITY-102)

elseif(cls == Class.class)

{

m = introspector.getMethod((Class)obj, methodName, args);

if(m !=null)

{

returnnewVelMethodImpl(m);

}

}

returnnull;

}

该方式实际调用Introspector.getMethod()方法。

publicMethod getMethod(finalClass c,finalString name,finalObject[] params)

throwsIllegalArgumentException

{

try

{

//调用父类IntrospectorBase.getMethod()方法

returnsuper.getMethod(c, name, params);

}

catch(MethodMap.AmbiguousException ae)

{

/*异常处理*/

}

returnnull;

}

Introspector.getMethod()实际只是扩展了其父类的getMethod方法,增加了异常日志功能。

IntrospectorBase.getMethod()代码如下:

publicMethod getMethod(finalClass c,finalString name,finalObject[] params)

throwsIllegalArgumentException,MethodMap.AmbiguousException

{

if(c ==null)

{

thrownewIllegalArgumentException ("class object is null!");

}

if(params ==null)

{

thrownewIllegalArgumentException("params object is null!");

}

IntrospectorCache ic = getIntrospectorCache();

ClassMap classMap = ic.get(c);

if(classMap ==null)

{

classMap = ic.put(c);

}

returnclassMap.findMethod(name, params);

}

该方法首先获取从IntrospectorCache中获取表示类信息的classMap,如果没找到则在cache中put该类型信息。有意思的是这里没有常见的缓存未命中直接查询的过程,而是直接更新缓存,也就意味着put方法里有构造类型信息的过程。

IntrospectorCache.put()代码如下

.......

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值