一、何为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中,其主要类图结构如下:
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代码如下:
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()代码如下
.......