Groovy解析之深入理解元编程原理

3,618 阅读15分钟

Groovy解析之元编程原理

引言

我们知道,Groovy 和 Java 一样是运行在 JVM上 的,而且 Groovy 代码最终也是编译成 Java 字节码;但是 Groovy 却是一门动态语言,可以在运行时扩展程序,比如动态调用(拦截、注入、合成)方法,那么 Groovy 是如何实现这一切的呢?

动态调用入口:CallSite

其实这一切都要归功于 Groovy 编译器,Groovy 编译器在编译 Groovy 代码的时候,并不是像 Java 一样,直接编译成字节码,而是编译成 “动态调用的字节码”。

例如下面这一段 Groovy 代码:

package groovy

println("Hello World!")

当我们用Groovy编译器编译之后,就会变成:

package groovy;

......

public class HelloGroovy extends Script {
    private static /* synthetic */ ClassInfo $staticClassInfo;
    public static transient /* synthetic */ boolean __$stMC;
    private static /* synthetic */ ClassInfo $staticClassInfo$;
    private static /* synthetic */ SoftReference $callSiteArray;
    ......
    public static void main(String ... args) {
        // 调用runScript()方法
        CallSite[] arrcallSite = HelloGroovy.$getCallSiteArray();
        arrcallSite[0].call(InvokerHelper.class, HelloGroovy.class, (Object)args);
    }

    public Object run() {
        // 调用println()方法
        CallSite[] arrcallSite = HelloGroovy.$getCallSiteArray();
        return arrcallSite[1].callCurrent((GroovyObject)this, (Object)"Hello World!");
    }
    ......
    private static /* synthetic */ void $createCallSiteArray_1(String[] arrstring) {
        arrstring[0] = "runScript";
        arrstring[1] = "println";
    }
    ......
}

简单的一行代码,经过 Groovy 编译器编译之后,变得如此复杂。而这就是 Groovy 编译器做的,将普通的代码编译成可以动态调用的代码。

不难发现,经过编译之后,几乎所有的方法调用都变成通过 __CallSite__进行了,这个 CallSite 就是实现动态调用的入口,我们来看看这个 CallSite 都做了什么?

AbstractCallSite
package org.codehaus.groovy.runtime.callsite;

/**
 * Base class for all call sites
 */
public class AbstractCallSite implements CallSite {
    ......
    // call()方法是运行时方法调用的时候才触发的
    public Object call(Object receiver, Object arg1) throws Throwable {
        CallSite stored = this.array.array[this.index];
        return stored != this ? stored.call(receiver, arg1) : this.call(receiver, ArrayUtil.createArray(arg1));
    }
    ......
    public Object call(Object receiver, Object[] args) throws Throwable {
        return CallSiteArray.defaultCall(this, receiver, args);
    }
}

CallSite __ 主要负责__分发和缓存不同类型的方法调用逻辑,包括 callGetPropertySafe(), callGetProperty(), callGroovyObjectGetProperty(), callGroovyObjectGetPropertySafe(), call(), callCurrent(), callStatic(), callConstructor()等等

对于不同类型的方法调用需要通过不同的 CallSite 调用,因为针对不同类型的方法需要有不同的处理逻辑,否则可能会出现循环调用,抛出 StackOverflow 异常。例如对于当前对象(this)的方法调用需要通过 callCurrent(),对于static类型方法需要通过 callStatic(),而对于局部变量或者实例变量则是通过 call();

以 call(...) 方法为例,可以看出:call(...) 方法首先判断是否存在缓存,如果存在则直接调用,否则调用CallSiteArray.defaultCall(...) 方法创建并缓存 CallSite:

CallSiteArray
package org.codehaus.groovy.runtime.callsite;

public final class CallSiteArray {
    ......
    private static CallSite createCallSite(CallSite callSite, Object receiver, Object[] args) {
        if (receiver == null) {
            // null值
            return new NullCallSite(callSite);
        } else {
            CallSite site;
            if (receiver instanceof Class) {
                // 静态方法
                site = createCallStaticSite(callSite, (Class)receiver, args);
            } else if (receiver instanceof GroovyObject) {
                // Groovy对象方法
                site = createPogoSite(callSite, receiver, args);
            } else {
                // Java对象方法
                site = createPojoSite(callSite, receiver, args);
            }

            // 缓存CallSite
            replaceCallSite(callSite, site);
            return site;
        }
    }
    ......
    private static CallSite createPogoSite(CallSite callSite, Object receiver, Object[] args) {
        if (receiver instanceof GroovyInterceptable) {
            // 直接创建PogoInterceptableSite
            return new PogoInterceptableSite(callSite);
        } else {
            MetaClass metaClass = ((GroovyObject)receiver).getMetaClass();
            // 调用MetaClassImpl的createPogoCallSite()方法 或 直接创建PogoMetaClassSite
            return (CallSite)(metaClass instanceof MetaClassImpl ? ((MetaClassImpl)metaClass).createPogoCallSite(callSite, args) : new PogoMetaClassSite(callSite, metaClass));
        }
    }
    ......
}

createCallSite(...) 方法针对不同的目标对象的分别创建不同的CallSite,简单的来说:

  • 对于 null 值,创建 NullCallSite

  • 对于静态方法,创建 StaticMetaClassSite

  • 对于实现了 GroovyInterceptable 接口的 Groovy 对象,创建的是 PogoInterceptableSite。这也是为什么__实现了 GroovyInterceptable 接口的对象,任何方法调用最终都会调用 invokeMethod(...) 方法的原因。__

  • 对于普通 Groovy 对象的方法,创建的是 PogoMetaClassSite:

  • 对于普通 Java 对象的方法,创建的则是 PojoMetaClassSite

当然实际上,这个过程依然保持足够的开放和灵活性,并不是简单的根据方法类型来创建 CallSite,此处不再深入讨论,感兴趣的同学可以通过阅读源码一探究竟~

接下来,我们来看一下以 PogoMetaClassSite 为例,分析一下 CallSite 内部的方法调用分发逻辑:

PogoMetaClassSite
package org.codehaus.groovy.runtime.callsite;

public class PogoMetaClassSite extends MetaClassSite {
    ....
    public final Object call(Object receiver, Object[] args) throws Throwable {
        if (checkCall(receiver)) {
            try {
                try {
                    // 调用metaClass的invokeMethod方法
                    return metaClass.invokeMethod(receiver, name, args);
                } catch (MissingMethodException e) {
                    // metaClass中未找到相应的方法,调用失败
                    if (e instanceof MissingMethodExecutionFailed) {
                        throw (MissingMethodException)e.getCause();
                    } else if (receiver.getClass() == e.getType() && e.getMethod().equals(name)) {
                        // in case there's nothing else, invoke the object's own invokeMethod()
                        // 调用GroovyObject的invokeMethod方法
                        return ((GroovyObject)receiver).invokeMethod(name, args);
                    } else {
                        throw e;
                    }
                }
            } catch (GroovyRuntimeException gre) {
                throw ScriptBytecodeAdapter.unwrap(gre);
            }
        } else {
          return CallSiteArray.defaultCall(this, receiver, args);
        }
    }
    ......
}

PogoMetaClassSite 内部的逻辑比较简单,可以看出,方法调用逻辑最终是委托给 MetaClass 进行处理,如果 MetaClass 无法处理,则抛出异常或者调用 GroovyObjectinvokeMethod(...) 方法。

看到这里,其实我们已经可以看出 Groovy 实现动态特性的基本原理了:经过 Groovy 编译器编译之后,所有的方法调用都会通过 Groovy 构建的系统进行调用,而这个系统正是实现其动态特性的关键。

接下来,我们更进一步,分析一下 MetaClass 是如何分发方法调用的:

MetaClassImpl方法分发过程

invokeMethod
package groovy.lang;

/**
 * Allows methods to be dynamically added to existing classes at runtime
 * @see groovy.lang.MetaClass
 */
public class MetaClassImpl implements MetaClass, MutableMetaClass {
    ......
    public Object invokeMethod(Object object, String methodName, Object[] originalArguments) {
        return invokeMethod(theClass, object, methodName, originalArguments, false, false);
    }
    
    ......
    public Object invokeMethod(Class sender, Object object, String methodName, Object[] originalArguments, boolean isCallToSuper, boolean fromInsideClass) {
        ......
        // 查找是否存在符合条件的方法
        MetaMethod method = getMetaMethod(sender, object, methodName, isCallToSuper, arguments);

        final boolean isClosure = object instanceof Closure;
        if (isClosure) {
            // 针对closure的处理逻辑,此处省略
            ......
        }

        if (method != null) {
            // 方法存在,调用该方法
            return method.doMethodInvoke(object, arguments);
        } else {
            // 方法不存在,调用invokePropertyOrMissing()方法
            return invokePropertyOrMissing(object, methodName, originalArguments, fromInsideClass, isCallToSuper);
        }
    }
    
    ......
    private MetaMethod getMetaMethod(Class sender, Object object, String methodName, boolean isCallToSuper, Object... arguments) {
        MetaMethod method = null;
        if (CLOSURE_CALL_METHOD.equals(methodName) && object instanceof GeneratedClosure) {
            // 如果调用的是GeneratedClosure的call()方法,则查找doCall()方法
            method = getMethodWithCaching(sender, "doCall", arguments, isCallToSuper);
        }
        if (method==null) {
            // 查找方法(允许优先使用前次查找的缓存)
            method = getMethodWithCaching(sender, methodName, arguments, isCallToSuper);
        }
        MetaClassHelper.unwrap(arguments);

        if (method == null)
            // 如果参数是List,则展开该List,再次查找
            method = tryListParamMetaMethod(sender, methodName, isCallToSuper, arguments);
        return method;
    }
    
    ......
    private MetaMethod tryListParamMetaMethod(Class sender, String methodName, boolean isCallToSuper, Object[] arguments) {
        MetaMethod method = null;
        if (arguments.length == 1 && arguments[0] instanceof List) {
            Object[] newArguments = ((List) arguments[0]).toArray();
            method = createTransformMetaMethod(getMethodWithCaching(sender, methodName, newArguments, isCallToSuper));
        }
        return method;
    }
    
    ......
    public MetaMethod getMethodWithCaching(Class sender, String methodName, Object[] arguments, boolean isCallToSuper) {
        // let's try use the cache to find the method
        if (!isCallToSuper && GroovyCategorySupport.hasCategoryInCurrentThread()) {
            // 查找方法(不允许优先使用前次查找的缓存,因为通过Category注入的方法优先级最高,需要重新查找)
            return getMethodWithoutCaching(sender, methodName, MetaClassHelper.convertToTypeArray(arguments), isCallToSuper);
        } else {
            final MetaMethodIndex.Entry e = metaMethodIndex.getMethods(sender, methodName);
            if (e == null)
              return null;

            // 查找super方法或者普通方法(允许优先使用前次查找的缓存)
            return isCallToSuper ? getSuperMethodWithCaching(arguments, e) : getNormalMethodWithCaching(arguments, e);
        }
    }
    
    ......  
}

首先,通过调用 getMetaMethod(...) 方法查找目标类及其父类是否存在该方法(包括参数类型兼容的方法):

  • 如果调用的是__GeneratedClosure__的 call(...) 方法,则转换成查找 doCall(...) 方法.
  • 如果调用的是 this 方法,则__其优先级依次为 1.目标类及其父类通过 Category 注入的方法、2.目标类及其父类通过 MetaClass(ExpandoMetaClass) 注入的方法、3.目标类及其父类定义的方法。同时还需 遵循定义在子类的方法优先于(覆盖)定义在父类的方法的原则 。__
  • 注意:针对 this 方法,此处只会查找 Category 注入的和已经在__方法列表(metaMethodIndex)__里的方法,而不会实时查找。实时查找的过程在下文的 invokeMissingMethod(...) 方法触发,另外实时查找会把找到(新添加)的方法保存到这个方法列表中。
  • 如果调用的是 super(...) 方法,则只会查找其父类(当前类的 MetaClassImpl 初始化的时候)已有的方法,也就是说 super 的调用是__部分动态调用__。(调用 super 方法的时候,如果父类不存在该方法,则依然会走invokePropertyOrMissing 方法(子类 property 优先))。
  • 如果未找到相关方法并且方法参数只有一个 List 类型的参数,则会__尝试展开该List__并再次通过上面的逻辑进行查找。

接着,如果目标对象是闭包(Closure),则需要走闭包的特殊处理逻辑,此处暂不讨论。

最后,如果找到了匹配的方法,则直接调用该方法;如果没有找到匹配的方法,则会调用 invokePropertyOrMissing(...) 方法。

接着我们来看看 invokePropertyOrMissing(...) 方法的处理逻辑:

invokePropertyOrMissing
package groovy.lang;

/**
 * Allows methods to be dynamically added to existing classes at runtime
 * @see groovy.lang.MetaClass
 */
public class MetaClassImpl implements MetaClass, MutableMetaClass {
    ......
    private Object invokePropertyOrMissing(Object object, String methodName, Object[] originalArguments, boolean fromInsideClass, boolean isCallToSuper) {
        // if no method was found, try to find a closure defined as a field of the class and run it
        Object value = null;
        // 查找property
        final MetaProperty metaProperty = this.getMetaProperty(methodName, false);
        if (metaProperty != null)
          value = metaProperty.getProperty(object);
        else {
            // 注意此处针对Map的特殊处理逻辑
            if (object instanceof Map)
              value = ((Map)object).get(methodName);
        }

        // 如果property是Closure,则调用其doCall()方法
        if (value instanceof Closure) {  // This test ensures that value != this If you ever change this ensure that value != this
            Closure closure = (Closure) value;
            MetaClass delegateMetaClass = closure.getMetaClass();
            return delegateMetaClass.invokeMethod(closure.getClass(), closure, CLOSURE_DO_CALL_METHOD, originalArguments, false, fromInsideClass);
        }

        // 如果目标对象是Script,则查找binding variables,并调用其call()方法
        if (object instanceof Script) {
            Object bindingVar = ((Script) object).getBinding().getVariables().get(methodName);
            if (bindingVar != null) {
                MetaClass bindingVarMC = ((MetaClassRegistryImpl) registry).getMetaClass(bindingVar);
                return bindingVarMC.invokeMethod(bindingVar, CLOSURE_CALL_METHOD, originalArguments);
            }
        }
        
        // 调用invokeMissingMethod()方法
        return invokeMissingMethod(object, methodName, originalArguments, null, isCallToSuper);
    }
    ......
}

invokePropertyOrMissing(...) 方法的调用过程:

  • 查找目标类及其父类是否存在与该方法同名的 Property,且该 Property 为 Closure 类型,则调用该Closure注意:这里并不是通过 MetaClass 注入方法的实现逻辑,通过 MetaClass 注入的方法不会以Property 的形式存在(getter和setter方法除外),而是以 ClosureMetaMethod 类型的数据保存在到目标类的方法列表中。

  • 如果未找到相关 Property,且目标对象是 Script 类型,则尝试查找是否存在以该方法名命名的BindingVariable,如果存在则调用其 call (...) 方法。

  • 如果以上尝试均失败了,则调用 invokeMissingMethod(...) 方法

invokeMissingMethod
package groovy.lang;

public class MetaClassImpl implements MetaClass, MutableMetaClass {
    ......
    private Object invokeMissingMethod(Object instance, String methodName, Object[] arguments, RuntimeException original, boolean isCallToSuper) {
        // 注意这段逻辑,仅针对非super.xxx() 调用
        if (!isCallToSuper) {
            Class instanceKlazz = instance.getClass();
            if (theClass != instanceKlazz && theClass.isAssignableFrom(instanceKlazz))
              instanceKlazz = theClass;

            Class[] argClasses = MetaClassHelper.castArgumentsToClassArray(arguments);
            
            // 查找并调用MixIn注入的方法
            MetaMethod method = findMixinMethod(methodName, argClasses);
            if(method != null) {
                onMixinMethodFound(method); // 空函数
                return method.invoke(instance, arguments);
            }

            // 遍历ClassHierarchy,再一次查找并调用MetaMethod或者SubClassMethod
            method = findMethodInClassHierarchy(instanceKlazz, methodName, argClasses, this);
            if(method != null) {
                // 将找到的方法保存到方法列表中
                onSuperMethodFoundInHierarchy(method);
                return method.invoke(instance, arguments);
            }

            // 遍历ClassHierarchy,查找并调用动态注入的invokeMethod()方法
            // still not method here, so see if there is an invokeMethod method up the hierarchy
            final Class[] invokeMethodArgs = {String.class, Object[].class};
            method = findMethodInClassHierarchy(instanceKlazz, INVOKE_METHOD_METHOD, invokeMethodArgs, this );
            if(method instanceof ClosureMetaMethod) {
                // 将找到的方法保存到方法列表中
                onInvokeMethodFoundInHierarchy(method);
                return method.invoke(instance, invokeMethodArgs);
            }

            // 查找并调用Category注入的methodMissing()方法
            // last resort look in the category
            if (method == null && GroovyCategorySupport.hasCategoryInCurrentThread()) {
                method = getCategoryMethodMissing(instanceKlazz);
                if (method != null) {
                    return method.invoke(instance, new Object[]{methodName, arguments});
                }
            }
        }

        // 尝试调用methodMissing()方法
        if (methodMissing != null) {
            try {
                return methodMissing.invoke(instance, new Object[]{methodName, arguments});
            } catch (InvokerInvocationException iie) {
                ......
            }
        }
    } 
    ......
}

invokeMissingMethod(...) 方法的调用过程如下,如果调用的是 this 方法,则:

  • 查找是否存在通过 MixIn 注入的方法,如果存在则调用该方法。

  • 遍历 ClassHierarchy(superClasses__和__interfaces),再一次查找是否存在符合条件的 MetaMethod 或者SubClassMethod 方法(如果存在多个,则返回匹配度最高的)。SubClassMethod 是定义在目标类或者实例范围内的动态方法,其作用域仅限于目标类或实例

  • 遍历 ClassHierarchy,查找是否存在 invokeMethod(...) 方法(如果存在多个,则返回匹配度最高的),且该方法是 ClosureMetaMethod 类型(即通过 MetaClass 注入的拦截方法),如果存在则调用该方法。

  • 如果通过以上逻辑还是查找不到任何我们要调用的方法信息,那么就会__尝试调用 methodMissing(...) 方法__。这里会优先调用通过Category注入的 methodMissing(...) 方法,如果未找到才调用(定义或注入的) methodMissing(...) 方法

以上几步均是针对this方法的处理逻辑,如果调用的是 super(...) 方法,那么会直接尝试调用(定义或注入的) methodMissing(...) 方法

最后,如果 methodMissing(...) 方法也不存在,就抛出 MissingMethodException 异常

至此,我们已经完整的分析了方法查找和分发的流程,也看到了__Category __和 MixIn 注入的方法是如何被调用到的。那么 Groovy 又是如何将 MetaClass 和类或者实例绑定在一起的呢?

MetaClass的初始化过程

首先,我们来分析一下 MetaClass 从创建到初始化的过程,以 GroovyObjectSupport 这个官方基类为例:

GroovyObjectSupport
package groovy.lang;

/**
 * A useful base class for Java objects wishing to be Groovy objects
 */
public abstract class GroovyObjectSupport implements GroovyObject {
    // never persist the MetaClass
    private transient MetaClass metaClass;

    public GroovyObjectSupport() {
        this.metaClass = getDefaultMetaClass();
    }
    ......
    public Object invokeMethod(String name, Object args) {
        return getMetaClass().invokeMethod(this, name, args);
    }
    ......
    private MetaClass getDefaultMetaClass() {
        return InvokerHelper.getMetaClass(this.getClass());
    }
}

默认的 MetaClass 是通过 InvokerHelper.getMetaClass(this.getClass()) 获取的。

InvokerHelper
package org.codehaus.groovy.runtime;

/**
 * A static helper class to make bytecode generation easier and act as a facade over the Invoker
 */
public class InvokerHelper {
    ......
    public static final MetaClassRegistry metaRegistry = GroovySystem.getMetaClassRegistry();
    ......
    public static MetaClass getMetaClass(Class cls) {
        return metaRegistry.getMetaClass(cls);
    }
    ......
}

而 InvokerHelper 则是通过 MetaClassRegistryImpl 创建并获取 MetaClass 的。接下来,我们来看一下 MetaClassRegistryImpl 是如何创建和初始化 MetaClass 的:

MetaClassRegistryImpl
package org.codehaus.groovy.runtime.metaclass;

/**
 * A registry of MetaClass instances which caches introspection &
 * reflection information and allows methods to be dynamically added to
 * existing classes at runtime
 */
public class MetaClassRegistryImpl implements MetaClassRegistry{
    ......
    public final MetaClass getMetaClass(Class theClass) {
        return ClassInfo.getClassInfo(theClass).getMetaClass();
    }
    ......
}

MetaClassRegistryImpl 是通过 ClassInfo 创建并获取 MetaClass 实例的:

ClassInfo
package org.codehaus.groovy.reflection;

/**
 * Handle for all information we want to keep about the class
 */
public class ClassInfo implements Finalizable {
    ......
    public final MetaClass getMetaClass() {
        MetaClass answer = getMetaClassForClass();
        if (answer != null) return answer;

        lock();
        try {
            return getMetaClassUnderLock();
        } finally {
            unlock();
        }
    }
    
    ......
    private MetaClass getMetaClassUnderLock() {
        // 是否已经创建有缓存
        MetaClass answer = getStrongMetaClass();
        if (answer!=null) return answer;
        
        answer = getWeakMetaClass();
        final MetaClassRegistry metaClassRegistry = GroovySystem.getMetaClassRegistry();
        MetaClassRegistry.MetaClassCreationHandle mccHandle = metaClassRegistry.getMetaClassCreationHandler();
        
        if (isValidWeakMetaClass(answer, mccHandle)) {
            return answer;
        }

        // 创建MetaClass实例
        answer = mccHandle.create(classRef.get(), metaClassRegistry);
        answer.initialize();

        // 缓存MetaClass实例
        if (GroovySystem.isKeepJavaMetaClasses()) {
            setStrongMetaClass(answer);
        } else {
            setWeakMetaClass(answer);
        }
        return answer;
    }
    ......
}

ClassInfo是实际上创建和保存MetaClass的类,如果已创建则直接返回;如果未创建则创建并保存MetaClass。每个类都绑定一个MetaClass实例以及一个ClassInfo实例,用于保存相关信息

接下来,我们来看一下MetaClassImpl初始化的过程:

MetaClassImpl
package groovy.lang;

public class MetaClassImpl implements MetaClass, MutableMetaClass {
    ......
    public synchronized void initialize() {
        if (!isInitialized()) {
            // 解析目标类及其父类的所有方法,并保存到方法列表
            fillMethodIndex();
            try {
                // 解析目标类及其父类的所有属性,并保存到属性列表
                addProperties();
            } catch (Throwable e) {
                if (!AndroidSupport.isRunningAndroid()) {
                    UncheckedThrow.rethrow(e);
                }
                // Introspection failure...
                // May happen in Android
            }
            initialized = true;
        }
    }
    ......
}

MetaClassImpl 在初始化的时候就会通过反射解析目标类及其父类的所有方法(Methods)和属性(Fields和Setters以及Getters),甚至包括其父类已经动态注入的方法(getNewMetaMethods),并保存到方法列表和属性列表。

那么,如果父类在子类已经初始化 MetaClass 之后再动态注入的方法,子类是不是就存在调用不到该方法的可能呢?事实上,在上文我们分析 invokeMissingMethod(...) 这个方法的时候,提到__如果调用的是 this 方法,则会遍历 ClassHierarchy 再次查找方法,其中就包含 superClasses 或者 interfaces 在初始化之后动态注入的方法。而如果是 super(...) 方法,则无此逻辑。__因此,可以看出,在子类初始化之后再注入父类的方法其优先级是非常低的。

至此,我们已经讲完了 MetaClassImpl 从创建到初始化的过程。但是还有几个问题没有弄清楚,比如 Groovy 官方提供的方法(例如use方法)是如何注入的呢?以及 ExtensionModule 又是如何注入的?

Groovy系统方法的初始化过程

MetaClassRegistryImpl

我们不妨先来看一下 MetaClassRegistryImpl 在初始化的时候都做了什么?

package org.codehaus.groovy.runtime.metaclass;

/**
 * A registry of MetaClass instances which caches introspection &
 * reflection information and allows methods to be dynamically added to
 * existing classes at runtime
 */
public class MetaClassRegistryImpl implements MetaClassRegistry{
    ......
    public MetaClassRegistryImpl(final int loadDefault, final boolean useAccessible) {
        this.useAccessible = useAccessible;

        if (loadDefault == LOAD_DEFAULT) {
            final Map<CachedClass, List<MetaMethod>> map = new HashMap<CachedClass, List<MetaMethod>>();

            // let's register the default methods
            // 注册DefaultGroovyMethods
            registerMethods(null, true, true, map);
            final Class[] additionals = DefaultGroovyMethods.ADDITIONAL_CLASSES;
            for (int i = 0; i != additionals.length; ++i) {
                createMetaMethodFromClass(map, additionals[i]);
            }

            // 注册java版本兼容实例方法
            Class[] pluginDGMs = VMPluginFactory.getPlugin().getPluginDefaultGroovyMethods();
            for (Class plugin : pluginDGMs) {
                registerMethods(plugin, false, true, map);
            }
            
            // 注册DefaultGroovyStaticMethods
            registerMethods(DefaultGroovyStaticMethods.class, false, false, map);
            
            // 注册java版本兼容静态方法
            Class[] staticPluginDGMs = VMPluginFactory.getPlugin().getPluginStaticGroovyMethods();
            for (Class plugin : staticPluginDGMs) {
                registerMethods(plugin, false, false, map);
            }

            // 解析注册ExtensionModule方法
            ExtensionModuleScanner scanner = new ExtensionModuleScanner(new DefaultModuleListener(map), this.getClass().getClassLoader());
            scanner.scanClasspathModules();

            refreshMopMethods(map);

        }

        installMetaClassCreationHandle();

        final MetaClass emcMetaClass = metaClassCreationHandle.create(ExpandoMetaClass.class, this);
        emcMetaClass.initialize();
        ClassInfo.getClassInfo(ExpandoMetaClass.class).setStrongMetaClass(emcMetaClass);
        ......
    }
    
    ......
    private void registerMethods(final Class theClass, final boolean useMethodWrapper, final boolean useInstanceMethods, Map<CachedClass, List<MetaMethod>> map) {
        if (useMethodWrapper) {
            // Here we instantiate objects representing MetaMethods for DGM methods.
            // Calls for such meta methods done without reflection, so more effectively.

            try {
                // 解析dgminfo文件,并注册相关方法
                List<GeneratedMetaMethod.DgmMethodRecord> records = GeneratedMetaMethod.DgmMethodRecord.loadDgmInfo();

                for (GeneratedMetaMethod.DgmMethodRecord record : records) {
                    Class[] newParams = new Class[record.parameters.length - 1];
                    System.arraycopy(record.parameters, 1, newParams, 0, record.parameters.length-1);

                    MetaMethod method = new GeneratedMetaMethod.Proxy(
                            record.className,
                            record.methodName,
                            ReflectionCache.getCachedClass(record.parameters[0]),
                            record.returnType,
                            newParams
                    );
                    final CachedClass declClass = method.getDeclaringClass();
                    List<MetaMethod> arr = map.get(declClass);
                    if (arr == null) {
                        arr = new ArrayList<MetaMethod>(4);
                        map.put(declClass, arr);
                    }
                    arr.add(method);
                    instanceMethods.add(method);
                }
            } catch (Throwable e) {
                e.printStackTrace();
                // we print the error, but we don't stop with an exception here
                // since it is more comfortable this way for development
            }
        } else {
            CachedMethod[] methods = ReflectionCache.getCachedClass(theClass).getMethods();

            for (CachedMethod method : methods) {
                final int mod = method.getModifiers();
                if (Modifier.isStatic(mod) && Modifier.isPublic(mod) && method.getCachedMethod().getAnnotation(Deprecated.class) == null) {
                    CachedClass[] paramTypes = method.getParameterTypes();
                    if (paramTypes.length > 0) {
                        List<MetaMethod> arr = map.get(paramTypes[0]);
                        if (arr == null) {
                            arr = new ArrayList<MetaMethod>(4);
                            map.put(paramTypes[0], arr);
                        }
                        if (useInstanceMethods) {
                            // 实例方法
                            final NewInstanceMetaMethod metaMethod = new NewInstanceMetaMethod(method);
                            arr.add(metaMethod);
                            instanceMethods.add(metaMethod);
                        } else {
                            // 静态方法
                            final NewStaticMetaMethod metaMethod = new NewStaticMetaMethod(method);
                            arr.add(metaMethod);
                            staticMethods.add(metaMethod);
                        }
                    }
                }
            }
        }
    }
}

MetaClassRegistryImpl 在其实例化的时候就会通过 registerMethods(...) 方法去加载默认的方法,包括:

  • 加载 DefaultGroovyMethod 即 DgmMethod。DgmMethod 是一系列定义在 org.codehaus.groovy.runtime 包下,以 dgm$n 命名的方法类,其信息存储在 /META-INF/dgminfo 文件上。registerMethods(...) 先读取该文件的内容,然后根据文件内容载入这些方法,并以 NewInstanceMetaMethod(实例方法) 的形式保存到各自定义类的方法列表上。这些方法是 Groovy 系统提供的一些动态方法,例如use,with等等。这就是为什么我们可以直接使用这些方法的原因:MetaClassRegistryImpl 在实例化的时候就把这些方法加载进来了,而且其目标类普遍为基类,如Objec、String、Collection等等,这就保证了我们可以在这些类及其子类上正常的调用这些方法。

  • 通过 registerMethods(...) 加载 VMPluginFactory.getPlugin().getPluginDefaultGroovyMethods() 这个方法返回的所有类的静态方法,并以 NewInstanceMetaMethod(实例方法) 的形式保存到目标类(方法的第一个参数对应的类)的方法信息上,然后在调用的时候转换成调用静态方法,并插入调用的对象作为第一个参数(类似于通过Category注入方法)。这些方法是为了支持不同版本的JVM而提供的兼容方法,不需要过多关注。

  • 通过 registerMethods(...) 去加载 DefaultGroovyStaticMethods 类提供的静态方法。这些静态方法会以 NewStaticMetaMethod(静态方法) 的形式保存到目标类(方法的第一个参数对应的类)的方法列表上,然后在调用的时候插入一个null值作为第一个参数,例如 Thread.start() 方法等等。

  • 通过 registerMethods(...) 加载 VMPluginFactory.getPlugin().getPluginStaticGroovyMethods() 这个方法返回的所有类的静态方法,以 NewStaticMetaMethod(静态方法) 的形式保存到目标类(方法的第一个参数对应的类)的方法信息上,然后在调用的时候插入一个 null 值作为第一个参数。这些方法也是为了支持不同版本的JVM而提供的兼容方法,不需要过多关注。

  • 最后,通过 ExtensionModuleScanner 加载默认的 ExtensionModule 方法

动态注入原理:ExpandoMetaClass

ExpandoMetaClass

上文我们提到类或者实例默认绑定的 MetaClass 是 MetaClassImpl 类型的实例,但是事实上,当我们通 metaClass 注入方法的时候,其实是通过 ExpandoMetaClass 注入的,我们不妨简单看一下 ExpandoMetaClass 的代码:

package groovy.lang;

public class ExpandoMetaClass extends MetaClassImpl implements GroovyObject {
    ......
    private final Set<MetaMethod> inheritedMetaMethods = new HashSet<MetaMethod>();
    private final Map<String, MetaProperty> beanPropertyCache = new ConcurrentHashMap<String, MetaProperty>(16, 0.75f, 1);
    private final Map<String, MetaProperty> staticBeanPropertyCache = new ConcurrentHashMap<String, MetaProperty>(16, 0.75f, 1);
    private final Map<MethodKey, MetaMethod> expandoMethods = new ConcurrentHashMap<MethodKey, MetaMethod>(16, 0.75f, 1);

    public Collection getExpandoSubclassMethods() {
        return expandoSubclassMethods.values();
    }

    private final ConcurrentHashMap expandoSubclassMethods = new ConcurrentHashMap(16, 0.75f, 1);
    private final Map<String, MetaProperty> expandoProperties = new ConcurrentHashMap<String, MetaProperty>(16, 0.75f, 1);
    private ClosureStaticMetaMethod invokeStaticMethodMethod;
    private final Set<MixinInMetaClass> mixinClasses = new LinkedHashSet<MixinInMetaClass>();
    
    ......
    // 调用实例方法
    public Object invokeMethod(Class sender, Object object, String methodName, Object[] originalArguments, boolean isCallToSuper, boolean fromInsideClass) {
        // 判断是否通过metaClass注册了 invokeMethod() 方法
        if (invokeMethodMethod != null) {
            MetaClassHelper.unwrap(originalArguments);
            return invokeMethodMethod.invoke(object, new Object[]{methodName, originalArguments});
        }
        return super.invokeMethod(sender, object, methodName, originalArguments, isCallToSuper, fromInsideClass);
    }
    
    ......
    // 调用静态方法
    public Object invokeStaticMethod(Object object, String methodName, Object[] arguments) {
        // 判断是否通过metaClass注册了 invokeStaticMethod() 方法
        if (invokeStaticMethodMethod != null) {
            MetaClassHelper.unwrap(arguments);
            return invokeStaticMethodMethod.invoke(object, new Object[]{methodName, arguments});
        }
        return super.invokeStaticMethod(object, methodName, arguments);
    }
    
    ...... 
    // 动态注入方法
    public void setProperty(String property, Object newValue) {
        if (newValue instanceof Closure) {
            if (property.equals(CONSTRUCTOR)) {
                property = GROOVY_CONSTRUCTOR;
            }
            Closure callable = (Closure) newValue;
            final List<MetaMethod> list = ClosureMetaMethod.createMethodList(property, theClass, callable);
            for (MetaMethod method : list) {
                // here we don't care if the method exists or not we assume the
                // developer is responsible and wants to override methods where necessary
                // 注册绑定类实例方法
                registerInstanceMethod(method);
            }
        } else {
            registerBeanProperty(property, newValue);
        }
    }
    
    ......
    // 动态注入方法
     public Object invokeMethod(String name, Object args) {
        final Object[] argsArr = args instanceof Object[] ? (Object[]) args : new Object[]{args};
        MetaMethod metaMethod = myMetaClass.getMetaMethod(name, argsArr);
        if (metaMethod != null) {
            // we have to use doMethodInvoke here instead of simply invoke,
            // because getMetaMethod may provide a method that can not be called
            // without further argument transformation, which is done only in 
            // doMethodInvoke
            return metaMethod.doMethodInvoke(this, argsArr);
        }

        if (argsArr.length == 2 && argsArr[0] instanceof Class && argsArr[1] instanceof Closure) {
            if (argsArr[0] == theClass)
                // 注册绑定类的实例方法
                registerInstanceMethod(name, (Closure) argsArr[1]);
            else {
                // 注册Subclass实例方法
                registerSubclassInstanceMethod(name, (Class) argsArr[0], (Closure) argsArr[1]);
            }
            return null;
        }

        if (argsArr.length == 1 && argsArr[0] instanceof Closure) {
            registerInstanceMethod(name, (Closure) argsArr[0]);
            return null;
        }

        throw new MissingMethodException(name, getClass(), argsArr);
    }
    ......
}

ExpandoMetaClass 保存了动态注入的方法和属性的信息,其中包括:

  • mixinClasses 是通过 MixIn 动态注入的类信息

  • invokeMethodMethod 是动态注入的 “invokeMethod(...)” 方法

  • invokeStaticMethodMethod 是动态注入的 “invokeStaticMethod(...)” 方法

  • expandoMethods 是通过 metaClass 动态注入的方法集合

  • expandoSubclassMethods 是通过 metaClass动态 注入的 subclass 方法集合

  • beanPropertyCache 是动态注入的实例属性集合

  • staticBeanPropertyCache 是动态注入的静态属性集合

另外,从 ExpandoMetaClass 的代码,我们也可以看出,如果一个类或实例,通过 metaClass 注入了 “invokeMethod(...)” 拦截方法,那么任何的方法调用都会调用该方法;

由上文可知,当我们设置属性值或者调用方法的时候,如果该属性或者方法不存在,则会调用 setProperty(...)invokeMethod(...)(注意区分是哪个invokeMethod),这条规则当然也适用于 ExpandoMetaClass。而 ExpandoMetaClass 正是利用这条规则实现动态注入方法:在调用 setProperty(...)invokeMethod(...) 时检查参数是否是 Closure 类型的,如果是且符合其他条件,则将该 Closure 封装成 MetaMethod 并保存到方法列表中。

当然 ExpandoMetaClass 实现动态注入方法的逻辑远不止如此(例如MixIn),此处不再深入分析,感兴趣的同学可以阅读 ExpandoMetaClass 源码,一探究竟。

HandleMetaClass

既然类或者实例默认绑定的 MetaClass 是 MetaClassImpl 类的实例,那么当我们通过 metaClass 动态注入方法的时候,又是__如何切换到 ExpandoMetaClass 的__呢?

事实上,当我们通过metaClass注入方法的时候,例如下面这一段代码:

String.metaClass.sayHello = { ->
    "Hello ${delegate}!"
}

Groovy编译器会将其编译成:

......
public Object run() {
    _run_closure1 _run_closure12 = new _run_closure1((Object)this, (Object)this);
    ScriptBytecodeAdapter.setProperty(
        (Object)((Object)_run_closure12), null,
        ( Object)arrcallSite[1].callGetProperty(String.class),
        (String)"sayHello"
    );       
}

......
private static /* synthetic */ void $createCallSiteArray_1(String[] arrstring) {
    arrstring[0] = "runScript";
    arrstring[1] = "metaClass";
}

我们发现,Groovy会将这个 .metaClass 调用编译成 getProperty(String.class,"metaClass")。上文我们说到,MetaClassImplRegistry 在实例化的时候,即会加载 GroovyDefaultMethod,其中就包括定义在 Class 类及Object 类上的 getMetaClass(...) 方法。因此 getProperty(String.class,"metaClass") 这个方法最终会调用到定义在 DefaultGroovyMethods 中的这个 getMetaClass(...) 方法。

这里面涉及到一个知识点: Groovy的getProperty(...) 方法除了查找类中定义的 Field 之外,还会查找动态注入的 Property 以及 Getter() 方法。 与此同时 getAttribute(...) 方法却只会查找类中定义的 Field。

package org.codehaus.groovy.runtime;

public class DefaultGroovyMethods extends DefaultGroovyMethodsSupport {
    ......
    public static MetaClass getMetaClass(Class c) {
        MetaClassRegistry metaClassRegistry = GroovySystem.getMetaClassRegistry();
        MetaClass mc = metaClassRegistry.getMetaClass(c);
        if (mc instanceof ExpandoMetaClass
                || mc instanceof DelegatingMetaClass && ((DelegatingMetaClass) mc).getAdaptee() instanceof ExpandoMetaClass)
            return mc;
        else {
            // 创建HandleMetaClass
            return new HandleMetaClass(mc);
        }
    }
    
    public static MetaClass getMetaClass(Object obj) {
        MetaClass mc = InvokerHelper.getMetaClass(obj);
        // 创建HandleMetaClass
        return new HandleMetaClass(mc, obj);
    }
    
    public static MetaClass getMetaClass(GroovyObject obj) {
        // we need this method as trick to guarantee correct method selection
        return getMetaClass((Object)obj);
    }
    ......
}

从源码可以看出,当我们以 String.metaClass 这种形式访问 metaClass 这个 Property 的时候,如果 String 这个类绑定的 MetaClass 不是 ExpandoMetaClass 类型的实例,那么就会创建并返回一个 HandleMetaClass 类的实例。

HandleMetaClass 只是一个代理类,封装和延迟了创建 ExpandoMetaClass 的时机

package org.codehaus.groovy.runtime;

public class HandleMetaClass extends DelegatingMetaClass {
    ......
    public void initialize() {
        replaceDelegate();
        delegate.initialize();
    }

    /**
     * 创建和更新类或实例绑定的MetaClass
     */
    public GroovyObject replaceDelegate() {
        if (object == null) {
            // 绑定类
            if (!(delegate instanceof ExpandoMetaClass)) {
              // 创建和初始化ExpandoMetaClass
              delegate = new ExpandoMetaClass(delegate.getTheClass(), true, true);
              delegate.initialize();
            }
            DefaultGroovyMethods.setMetaClass(delegate.getTheClass(), delegate);
        }
        else {
          // 绑定实例
          if (object != NONE) {
              final MetaClass metaClass = delegate;
              // 创建和初始化ExpandoMetaClass
              delegate = new ExpandoMetaClass(delegate.getTheClass(), false, true);
              if (metaClass instanceof ExpandoMetaClass) {
                  ExpandoMetaClass emc = (ExpandoMetaClass) metaClass;
                  for (MetaMethod method : emc.getExpandoMethods())
                    ((ExpandoMetaClass)delegate).registerInstanceMethod(method);
              }
              delegate.initialize();
              MetaClassHelper.doSetMetaClass(object, delegate);
              object = NONE;
          }
        }
        return (GroovyObject)delegate;
    }

    /** 
     * 方法调用也会触发更新MetaClass
     */
    public Object invokeMethod(String name, Object args) {
        return replaceDelegate().invokeMethod(name, args);
    }
    ......
}

HandleMetaClass 在初始化或者调用到相关方法的时候,就会为这个类或实例创建一个新的ExpandoMetaClass,并更新该类或对象所绑定的 MetaClass 信息。此时,我们就可以通过 metaClass 动态注入方法了。

因为每个类或实例所绑定的 ExpandoMetaClass 是唯一的,由此可知 在类上动态注入的方法是全局,而在实例上动态注入的方法则是局部的

Category实现原理

MixIn实现原理

ExtensionModule实现原理

接下来,我们来分析一下 ExtensionModule 是如何实现,RTFSC:

package groovy.grape

/**
 * Implementation supporting {@code @Grape} and {@code @Grab} annotations based on Ivy.
 */
class GrapeIvy implements GrapeEngine {
    ......
    @CompileStatic
    private processCategoryMethods(ClassLoader loader, File file) {
        // register extension methods if jar
        if (file.name.toLowerCase().endsWith(".jar")) {
            def mcRegistry = GroovySystem.metaClassRegistry
            if (mcRegistry instanceof MetaClassRegistryImpl) {
                try (JarFile jar = new JarFile(file)) {
                    // 查找META-INF文件夹
                    def entry = jar.getEntry(ExtensionModuleScanner.MODULE_META_INF_FILE)
                    if (!entry) {
                        entry = jar.getEntry(ExtensionModuleScanner.LEGACY_MODULE_META_INF_FILE)
                    }
                    if (entry) {
                        Properties props = new Properties()

                        // 读取ExtensionModule文件
                        try (InputStream is = jar.getInputStream(entry)) {
                            props.load(is)
                        }

                        // 解析并注册相关方法
                        Map<CachedClass, List<MetaMethod>> metaMethods = new HashMap<CachedClass, List<MetaMethod>>()
                        mcRegistry.registerExtensionModuleFromProperties(props, loader, metaMethods)
                        // add old methods to the map
                        metaMethods.each { CachedClass c, List<MetaMethod> methods ->
                            // GROOVY-5543: if a module was loaded using grab, there are chances that subclasses
                            // have their own ClassInfo, and we must change them as well!
                            Set<CachedClass> classesToBeUpdated = [c].toSet()
                            ClassInfo.onAllClassInfo { ClassInfo info ->
                                if (c.theClass.isAssignableFrom(info.cachedClass.theClass)) {
                                    classesToBeUpdated << info.cachedClass
                                }
                            }
                            classesToBeUpdated*.addNewMopMethods(methods)
                        }
                    }
                } catch(ZipException zipException) {
                    throw new RuntimeException("Grape could not load jar '$file'", zipException)
                }
            }
        }
    }
    ......
}

Groovy 在加载依赖的 jar 包的时候,会查找是否存在 "META-INF/services/org.codehaus.groovy.runtime.ExtensionModule" 或者 "META-INF/groovy/org.codehaus.groovy.runtime.ExtensionModule" 这两个文件,如果存在则说明定义了ExtensionModule,那么就会读取这两个文件的内容。

package org.codehaus.groovy.runtime.metaclass;

/**
 * A registry of MetaClass instances which caches introspection &
 * reflection information and allows methods to be dynamically added to
 * existing classes at runtime
 */
public class MetaClassRegistryImpl implements MetaClassRegistry{
    ......
    public void registerExtensionModuleFromProperties(final Properties properties, final ClassLoader classLoader, final Map<CachedClass, List<MetaMethod>> map) {
        ExtensionModuleScanner scanner = new ExtensionModuleScanner(new DefaultModuleListener(map), classLoader);
        scanner.scanExtensionModuleFromProperties(properties);
    }
    
    ......
    private class DefaultModuleListener implements ExtensionModuleScanner.ExtensionModuleListener {
        private final Map<CachedClass, List<MetaMethod>> map;

        public DefaultModuleListener(final Map<CachedClass, List<MetaMethod>> map) {
            this.map = map;
        }

        public void onModule(final ExtensionModule module) {
            ......
            moduleRegistry.addModule(module);
            // register MetaMethods
            List<MetaMethod> metaMethods = module.getMetaMethods();
            for (MetaMethod metaMethod : metaMethods) {
                CachedClass cachedClass = metaMethod.getDeclaringClass();
                List<MetaMethod> methods = map.get(cachedClass);
                if (methods == null) {
                    methods = new ArrayList<MetaMethod>(4);
                    map.put(cachedClass, methods);
                }
                methods.add(metaMethod);
                if (metaMethod.isStatic()) {
                    // 静态方法
                    staticMethods.add(metaMethod);
                } else {
                    // 实例方法
                    instanceMethods.add(metaMethod);
                }
            }
        }
    }
}

读取 ExtensionModule文件 内容之后,就可以找到定义 ExtensionModule 方法的类,然后加载该类及其中定义的静态方法,并分别以 NewStaticMetaMethod(静态方法) 和 NewInstanceMetaMethod(实例方法) 的形式保存到目标类(方法的第一个参数对应的类)的方法列表中。__NewStaticMetaMethod(静态方法) __和 __NewInstanceMetaMethod(实例方法) __这两种方法在调用的时候会分别插入 null值当前对象 作为方法的第一个参数,然后再通过反射调用真正的 Method。

因此,当我们定义 StaticExtensionMethod 的时候,应慎用第一个参数,因为它的值可能为 null,这个参数只是为了标识目标类。而当我们定义 InstanceExtensionMethod 的时候,其第一个参数就是目标类的实例对象。