Groovy源代码分析(三)

2021SC@SDUSC

Groovy对Java类的扩展(下)

上次我们分析了groovy的几个核心扩展类,主要是几个XXXGroovyMethods,通过这几个类的扩展方法,可以让我们编写的POJO和POGO类有更多常用的方法,可以极大的提高我们的开发效率。从整体上知道groovy SDK对JDK做了哪些扩展。

了解了这些扩展以后,我产生了一个疑问,这些都是GDK提供的新类,这些方法并没有加到JDK中对应的类中,我们是如何可以直接调用它扩展的那些方法的呢,例如,我们DefaultGroovyMethods类中为我们任意对象都提供了一个each方法,使用如下:

[1,2,3,4,5].each{
   print it
}

这里[1,2,3,4,5]其实就是创建了一个Java的ArrayList,而JDK中的ArrayList并没有each这个方法,而each方法又是DefaultGroovyMethods这个类中的一个方法,为什么我们创建的类的对象就可以像调用自己内部的方法一样,调用DefaultGroovyMethods提供的方法呢,这就是我们这次重点要分析的内容。

下面,我们就通过源码来分析一下这个过程。

第一步: 在编译Groovy自身的代码时,groovy编译器会调用GDK中的DgmConvertero类的main方法来将DefaultGroovyMethods中所有的方法都生成一个包装类,生成的这个包装类继承于GeneratedMetaMethod类,而GeneratedMetaMethod类则继承MetaMethod类。该包装类的类名类似于org.codehaus.groovy.runtime.dgm$123($后跟一个数),在Groovy分发的jar包中可以找到总共1317个这样的类,说明DGM中总共有1317个方法。下面我们就先来看一下DgmConvertero类的main方法是如何生成方法对应的包装类的,核心代码如下:

package org.codehaus.groovy.tools;

//生成DGM方法包装类的核心类
public class DgmConverter implements Opcodes {
    public DgmConverter() {
    }

    //生成DGM方法包装类的核心方法
    public static void main(String[] args) throws IOException {
        String targetDirectory = "target/classes/";
        boolean info = args.length == 1 && args[0].equals("--info") || args.length == 2 && args[0].equals("--info");
        if (info && args.length == 2) {
            targetDirectory = args[1];
            if (!targetDirectory.endsWith("/")) {
                targetDirectory = targetDirectory + "/";
            }
        }

        List<CachedMethod> cachedMethodsList = new ArrayList();
        /**
         * 
        Class[] DGM_LIKE_CLASSES = new Class[]{DefaultGroovyMethods.class,     DateGroovyMethods.class,EncodingGroovyMethods.class, IOGroovyMethods.class, ProcessGroovyMethods.class,
   ResourceGroovyMethods.class, SocketGroovyMethods.class, StringGroovyMethods.class}; 
这里我把DefaultGroovyMethods.DGM_LIKE_CLASSES的代码注释进来,可以清楚的看到var4这个List就是我们所有的XXXGroovyMethods的类字节码
        **/
        Class[] var4 = DefaultGroovyMethods.DGM_LIKE_CLASSES;
        int var5 = var4.length;

        //将所有XXXGroovyMethods类中的方法都统一添加到一个Collections中去,放到一个集合中(就是我们刚刚上面定义的cachedMethodsList),方便我们后面的处理
        int cur;
        for(cur = 0; cur < var5; ++cur) {
            Class aClass = var4[cur];
            Collections.addAll(cachedMethodsList, ReflectionCache.getCachedClass(aClass).getMethods());
        }

        CachedMethod[] cachedMethods = (CachedMethod[])cachedMethodsList.toArray(CachedMethod.EMPTY_ARRAY);
        List<DgmMethodRecord> records = new ArrayList();
        cur = 0;
        CachedMethod[] var25 = cachedMethods;
        int var8 = cachedMethods.length;

        //开始遍历整个cachedMethodsList中右存的方法
        for(int var9 = 0; var9 < var8; ++var9) {
            CachedMethod method = var25[var9];
            //只对public static类型的方法生成对应的方法包装类
            if (method.isStatic() && method.isPublic() && method.getAnnotation(Deprecated.class) == null && method.getParameterTypes().length != 0) {
                Class returnType = method.getReturnType();
                //这里就是生成类的类名,一会我们会通过截图看到有许多这样的类,这些类就是在这个时候动态生成的
                String className = "org/codehaus/groovy/runtime/dgm$" + cur++;
                 // 通过DgmMethodRecord类来记录一个方法中所有的信息
                DgmMethodRecord record = new DgmMethodRecord();
                records.add(record);
                record.methodName = method.getName();
                record.returnType = method.getReturnType();
                record.parameters = method.getNativeParameterTypes();
                record.className = className;
                //通过Groovy中的ClassWriter类来创建对应的包装类字节码,注意,不是生成源文件而是字节码,因为此时已经在编译器了,字节码才是真正可用的
                ClassWriter cw = new ClassWriter(1);
                cw.visit(47, 1, className, (String)null, "org/codehaus/groovy/reflection/GeneratedMetaMethod", (String[])null);
                //为类字节码创建各个方法 
                createConstructor(cw);
                String methodDescriptor = BytecodeHelper.getMethodDescriptor(returnType, method.getNativeParameterTypes());
                createInvokeMethod(method, cw, returnType, methodDescriptor);
                createDoMethodInvokeMethod(method, cw, className, returnType, methodDescriptor);
                createIsValidMethodMethod(method, cw, className);
                cw.visitEnd();
                byte[] bytes = cw.toByteArray();
                //将生成的byte[]字节码写入到最终的class文件中去,从而生成一个可用的class字节码 
                File targetFile = (new File(targetDirectory + className + ".class")).getCanonicalFile();
                targetFile.getParentFile().mkdirs();
                FileOutputStream fileOutputStream = new FileOutputStream(targetFile);

                try {
                    fileOutputStream.write(bytes);
                    fileOutputStream.flush();
                } catch (Throwable var22) {
                    try {
                        fileOutputStream.close();
                    } catch (Throwable var21) {
                        var22.addSuppressed(var21);
                    }

                    throw var22;
                }

                fileOutputStream.close();
            }
        }

        //将生成的包装类与DGM中对应的方法形成一个映射关系,存到dgminfo文件中去,这样才能正确的知道哪个方法对应着哪个包装类
        DgmMethodRecord.saveDgmInfo(records, targetDirectory + "/META-INF/dgminfo");
        if (info) {
            System.out.println("Saved " + cur + " dgm records to: " + targetDirectory + "/META-INF/dgminfo");
        }

    }

当这个方法执行完毕以后,我们所有的DGM(XXXGroovyMethods)类中的方法都对应的生成了一个dgmXXX类,如图:

3.0.9这个版本总共有1317多个这样的dgmXXX类。我们随便来看其中的一个类的代码:

public class dgm$17 extends GeneratedMetaMethod {
    public dgm$17(String var1, CachedClass var2, Class var3, Class[] var4) {
        super(var1, var2, var3, var4);
    }

    public Object invoke(Object var1, Object[] var2) {
        return DefaultTypeTransformation.box(DefaultGroovyMethods.any((Map)var1, (Closure)var2[0]));
    }

    public final Object doMethodInvoke(Object var1, Object[] var2) {
        var2 = this.coerceArgumentsToClasses(var2);
        return DefaultTypeTransformation.box(DefaultGroovyMethods.any((Map)var1, (Closure)var2[0]));
    }
}

从代码中,我们可以看到,这个dgm$17类,就是我们DefaultGroovyMethods类中的box()这个方法的包装类,其他的类则对应着其他的方法。

第二步,有了上面的各种包装类以后,我们接下来要看的一个类就是:MetaClassRegistryImpl,这个类是由groovy程序在运行前调用,他会为我们编译时生成的所有包装类再统一生成GeneratedMetaMethod.Proxy代理类,核心代码如下:

public class MetaClassRegistryImpl implements MetaClassRegistry {
  //最终执行的初始化函数
 public MetaClassRegistryImpl(int loadDefault, boolean useAccessible) {
    this.instanceMethods = new FastArray();
    this.staticMethods = new FastArray();
    this.changeListenerList = new LinkedList();
    this.nonRemoveableChangeListenerList = new LinkedList();
    this.metaClassInfo = new ManagedConcurrentLinkedQueue(ReferenceBundle.getWeakBundle());
    this.moduleRegistry = new ExtensionModuleRegistry();
    this.metaClassCreationHandle = new MetaClassCreationHandle();
    this.useAccessible = useAccessible;
    if(loadDefault == 0) {
      Map<CachedClass, List<MetaMethod>> map = new HashMap();
      //调用registerMethods来为所有的DefaultGroovyMethods中的包装类来创建Proxy
      this.registerMethods((Class)null, true, true, map);
      //以下的都是为指定的Class字节码中的方法创建Proxy,可以不用细看
      Class[] additionals = DefaultGroovyMethods.additionals;

      for(int i = 0; i != additionals.length; ++i) {
        this.createMetaMethodFromClass(map, additionals[i]);
      }

      Class[] pluginDGMs = VMPluginFactory.getPlugin().getPluginDefaultGroovyMethods();
      Class[] staticPluginDGMs = pluginDGMs;
      int var7 = pluginDGMs.length;

      int var8;
      for(var8 = 0; var8 < var7; ++var8) {
        Class plugin = staticPluginDGMs[var8];
        this.registerMethods(plugin, false, true, map);
      }

      this.registerMethods(DefaultGroovyStaticMethods.class, false, false, map);
      staticPluginDGMs = VMPluginFactory.getPlugin().getPluginStaticGroovyMethods();
      Class[] var13 = staticPluginDGMs;
      var8 = staticPluginDGMs.length;

      for(int var15 = 0; var15 < var8; ++var15) {
        Class plugin = var13[var15];
        this.registerMethods(plugin, false, false, map);
      }

      ExtensionModuleScanner scanner = new ExtensionModuleScanner(new MetaClassRegistryImpl.DefaultModuleListener(map), this.getClass().getClassLoader());
      scanner.scanClasspathModules();
      refreshMopMethods(map);
    }

    this.installMetaClassCreationHandle();
    MetaClass emcMetaClass = this.metaClassCreationHandle.create(ExpandoMetaClass.class, this);
    emcMetaClass.initialize();
    ClassInfo.getClassInfo(ExpandoMetaClass.class).setStrongMetaClass(emcMetaClass);
    this.addNonRemovableMetaClassRegistryChangeEventListener(new MetaClassRegistryChangeEventListener() {
      public void updateConstantMetaClass(MetaClassRegistryChangeEvent cmcu) {
        synchronized(MetaClassRegistryImpl.this.metaClassInfo) {
          MetaClassRegistryImpl.this.metaClassInfo.add(cmcu.getNewMetaClass());
          DefaultMetaClassInfo.getNewConstantMetaClassVersioning();
          Class c = cmcu.getClassToUpdate();
          DefaultMetaClassInfo.setPrimitiveMeta(c, cmcu.getNewMetaClass() == null);

          try {
            Field sdyn = c.getDeclaredField("__$stMC");
            sdyn.setBoolean((Object)null, cmcu.getNewMetaClass() != null);
          } catch (Throwable var7) {
            ;
          }

        }
      }
    });
  }
}

通过上面代码,我们可以知道,registerMethods()这个方法是最核心的,下面,我们就来看一下这个方法中的代码:

private void registerMethods(Class theClass, boolean useMethodWrapper, boolean useInstanceMethods, Map<CachedClass, List<MetaMethod>> map) {
  //由于我们上面第一处调用时传的true,所以我们只看if部分
 if(useMethodWrapper) {
      try {
        //从前面我们保存的dmginfo文件中取出我们在编译时生成的所有DgmMethodRecord类,所以如果useMethodWrapper为true,就是代表处理生成的所有包装类
        List<DgmMethodRecord> records = DgmMethodRecord.loadDgmInfo();
        Iterator var6 = records.iterator();
       //开始遍历所有DgmMethodRecord
        while(var6.hasNext()) {
          DgmMethodRecord record = (DgmMethodRecord)var6.next();
          Class[] newParams = new Class[record.parameters.length - 1];
          System.arraycopy(record.parameters, 1, newParams, 0, record.parameters.length - 1);
         //为每个DgmMethodRecord中保存的包装类生成一个Proxy对象。 
         MetaMethod method = new Proxy(record.className, record.methodName, ReflectionCache.getCachedClass(record.parameters[0]), record.returnType, newParams);
         //获取调用此方法所在的类
        CachedClass declClass = method.getDeclaringClass();

        List<MetaMethod> arr = (List)map.get(declClass);
          if(arr == null) {
            arr = new ArrayList(4);
            map.put(declClass, arr);
          }
          ((List)arr).add(method);
          //最重要,将生成的Proxy对象添加到instanceMethods(FastArray)中,
          this.instanceMethods.add(method);
        }
      } catch (Throwable var14) {
        var14.printStackTrace();
      }
    } else {
      //处理指定参数的类
      CachedMethod[] methods = ReflectionCache.getCachedClass(theClass).getMethods();
      CachedMethod[] var16 = methods;
      int var17 = methods.length;

      for(int var18 = 0; var18 < var17; ++var18) {
        CachedMethod method = var16[var18];
        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 = (List)map.get(paramTypes[0]);
            if(arr == null) {
              arr = new ArrayList(4);
              map.put(paramTypes[0], arr);
            }

            if(useInstanceMethods) {
              NewInstanceMetaMethod metaMethod = new NewInstanceMetaMethod(method);
              ((List)arr).add(metaMethod);
              this.instanceMethods.add(metaMethod);
            } else {
              NewStaticMetaMethod metaMethod = new NewStaticMetaMethod(method);
              ((List)arr).add(metaMethod);
              this.staticMethods.add(metaMethod);
            }
          }
        }
      }
    }
  }

于是我们知道,在groovy应用启动以后,就会首先由RootClassLoad去加载MetaClassRegistryImpl并调用其初始化函数,当他的初始化函数执行完毕以后,我们的每个DGM中的包装类,就有了一个对应的Proxy类。

第三步, Groovy本身的类在初始化完成以后,开始真正的执行我们自己的groovy代码,无论是脚本还是应用程序,这个时候当调用到对应的DGM方法时,首先看这个类中是否已经有此方法,有,则调用类中已有的,如果没有,则去MetaClassRegistryImpl中再去继续找此方法,如果找不到,找到调用,找不到,报methodmissingException。下面我们来看一下Proxy中的关键代码:
 

public static class Proxy extends GeneratedMetaMethod {
     private volatile MetaMethod proxy;
     private final String className;
      
     public Object invoke(Object object, Object[] arguments) {
         //最后就调用了对应包装类中的invoke方法,从而完成了我们整个的一个调用链
          return proxy().invoke(object, arguments);
     }
 
     public final synchronized MetaMethod proxy() {
        // 第一次调用时,通过createProxy创建包装类实例
         if (proxy == null) {
             createProxy();
         }
         return proxy;
     }
 
     private void createProxy() {
         try {
             // 载入包装类并进行实例化
             Class<?> aClass = getClass().getClassLoader().loadClass(className.replace('/','.'));
             Constructor<?> constructor = aClass.getConstructor(String.class, CachedClass.class, Class.class, Class[].class);
             proxy = (MetaMethod) constructor.newInstance(getName(), getDeclaringClass(), getReturnType(), getNativeParameterTypes());
         }
         catch (Throwable t) {
             t.printStackTrace();
             throw new GroovyRuntimeException("Failed to create DGM method proxy : " + t, t);
         }
    }
 }

到此,groovy中的XXXGroovyMethods类就通过我们以上分析的这个流程绑定到了Java对象中去,从而完成了对java中类的扩展。下面,我再将以上的整个流程用一个流程图来展示,让其更加的直观:

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值