`
bojiang
  • 浏览: 15009 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

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 源码例子

    ** Velocity 源码分析与应用实例 ** Velocity 是一个基于 Java 的模板引擎,它允许开发者将业务逻辑和页面展示分离,使得Web开发更加高效。Velocity 提供了一种简单但强大的语言来创建动态内容,其核心设计思想是...

    velocity实践——初识velocity

    NULL 博文链接:https://twb.iteye.com/blog/265761

    velocity 入门文档及应用源码,很适合做自动代码生成

    4. **velocity(1).rar** 和 **velocity.rar**:这两个RAR文件可能是Velocity的源码或相关示例项目的压缩包,供开发者下载研究。 **总结** Velocity作为一款强大的模板引擎,为Java开发提供了灵活的内容生成解决...

    Struts+Velocity整合示例(含源码)

    Action是处理用户请求的业务逻辑组件,Result则是Action执行后返回的结果,通常用来渲染视图。Interceptor则是拦截器,它可以对请求进行预处理和后处理,比如登录检查、日志记录等。Struts2通过配置文件或注解来定义...

    Velocity 分析

    源码分析可以帮助开发者更好地理解和定制Velocity以满足特定需求。此外,有一些工具可以帮助开发者更高效地使用Velocity,例如IDE插件、模板调试器等,它们可以提高开发效率并减少错误。 总的来说,Velocity是一个...

    三大框架整合 前端视图使用velocity渲染,数据访问层使用hibernate+mysql,

    1. **前端视图**:Velocity模板负责生成动态HTML,Spring MVC或Spring Web Flow可以配合Velocity,接收请求、处理业务逻辑,并将结果传递给Velocity模板进行渲染。 2. **数据访问层**:Hibernate作为JPA的实现,用于...

    velocity的所有jar包

    - **高效性能**:Velocity在编译模板后生成Java源码,然后由JVM执行,这使得其运行速度快且资源消耗低。 - **可扩展性**:Velocity Tools和其他扩展可以增加更多功能,如国际化、表单处理等。 然而,需要注意的是,...

    struts2+Velocity替换jsp项目源码

    3. **高性能**:相比JSP的编译过程,Velocity在首次渲染后,后续请求只需进行解释执行,效率更高。 4. **更少的服务器资源消耗**:Velocity不需要像JSP那样在每个请求时创建和销毁页面上下文,减轻了服务器负担。 *...

    velocity项目源代码

    Velocity模板语言(VTL)允许开发者在模板中嵌入Java代码片段,这些片段在运行时会被解析并执行,然后将结果插入到最终的输出中。这样,开发者可以专注于创建动态内容,而无需关心HTML的结构,使得模板更加专注和...

    velocity和freemarker的比较

    标题“velocity和freemarker的比较”涉及到的是两个在Java Web开发中常用的模板引擎——Velocity和FreeMarker。它们都是用于将静态模板与动态数据结合,生成HTML或其他类型的文本输出,常用于MVC(模型-视图-控制器...

    mat-velocity:提供velocity模板的渲染功能

    提供velocity模板的渲染功能 Installation npm install --save mat-velocity Usage var mat = require('mat') var velocity = require('mat-velocity') var rewrite = require('mat-rewrite') mat.task('daily', ...

    velocity-1.5.jar,velocity-1.6.2-dep.jar,velocity-tools-1.3.jar

    在实际使用中,开发者需要将这些JAR文件添加到项目的类路径中,然后可以通过创建VelocityContext对象,填充数据,最后使用Velocity Engine渲染模板。这整个过程是完全独立于具体的服务器环境的,使得Velocity成为一...

    velocity的jar包

    2. **模板语言**:Velocity模板语言(VTL)是 Velocity 的核心,它允许开发者在模板中嵌入Java代码,但不直接执行Java代码,而是通过VelocityEngine编译并执行。 3. **上下文(Context)**:Velocity中的Context对象...

    Velocity之HelloWorld配置

    ** Velocity之HelloWorld配置详解 ** Velocity是一款Java模板引擎,它是Apache软件基金会的Jakarta项目之一,主要用于生成动态Web内容。Velocity通过将内容展示与业务逻辑分离,使得开发者可以专注于后端逻辑,而...

    velocity-1.7-sources.zip

    velocity 的源代码 Velocity 是一个基于 Java 的模板引擎框架,提供的模板语言可以使用在 Java 中定义的对象和变量上。Velocity 是 Apache 基金会的项目,开发的目标是分离 MVC 模式中的持久化层和业务层。但是在...

    Struts2&&Velocity

    Struts2是一个MVC(Model-View-Controller)框架,它提供了处理用户请求、业务逻辑和视图展示的能力,而Velocity则是一个模板引擎,专注于视图层的渲染,使得开发者能够用简洁的语法来动态生成HTML或者其他类型的...

    JSP源码——tot-jsp-cms.zip

    《JSP源码分析——深度探索TOT-JSP-CMS系统》 JSP(JavaServer Pages)是一种基于Java技术的服务器端脚本语言,用于创建动态网页。TOT-JSP-CMS是一个使用JSP编写的网站内容管理系统,它为开发者提供了一个灵活、可...

    Velocity

    深入研究Velocity的源码有助于理解其工作原理,例如如何解析模板、如何执行指令、如何处理上下文对象等。这不仅可以帮助优化模板设计,还能为自定义扩展提供基础。 ### 学习资源 - 博文链接:...

    struts2整合velocity

    Action执行完成后,返回的“success”结果会对应到struts.xml中的配置,从而触发Velocity模板的渲染。 **4. 配置Velocity** 在Struts2中整合Velocity还需要配置Velocity相关的依赖,确保项目中包含Velocity Engine...

    Velocity--java的模板引擎

    **Velocity——Java的模板引擎** Velocity是Apache软件基金会下的一个开源项目,它是一个基于Java的模板引擎,专门用于生成动态Web内容。Velocity的目标是将HTML、XML等模板语言与业务逻辑分离,使得开发者可以专注...

Global site tag (gtag.js) - Google Analytics