2021SC@SDUSC
Groovy拓展方法与对象的关联方式 问题引入在上次的博客上分析Groovy的拓展方式时,我们举过这个例子:
String content = new File('config.txt').text
println content
我们知道text是IOGroovyMethods类里面的拓展方法,但这时,又有一个新的问题产生了,File类型怎么可以直接调用IOGroovyMethods里面的方法呢?就让我们在这节博客根据源码深入探讨一下这个问题。
Groovy反射由于在String类型里面并没有text方法,所以我们可以排除掉直接调用的可能性,那么使用反射的可能性就大大提高了。在分析那些拓展封装的方法,我们发现都有闭包Closure closure这样的一个参数和一个self,例如:
//DefaultGroovyMethods类中,用于遍历对象中的所有元素 public staticList each(List self, @ClosureParams(FirstGenericType.class) Closure closure) { return (List)each((Iterable)self, closure); }
而在点击进入查看闭包Closure类后,发现它继承于GroovyObjectSupport类,点击进入后,在其实现的接口GroovyObject中发现了反射的代码:
@Internal
default Object invokeMethod(String name, Object args) {
return this.getmetaClass().invokeMethod(this, name, args);
}
metaClass getmetaClass();
void setmetaClass(metaClass var1);
可见,在Groovy中一般是通过反射的方式调用方法的,具体来讲是通过metaClass中的invokeMothod方法来进行反射调用的。但是,我们知道,在Java中,通过反射来调用方法,比直接调用方法会慢上几倍。所以在Groovy中调用优化的思想就是通过直接调用来代替反射调用。而要实现直接调用,则需要为DefaultGroovyMethods(以下简称DGM)中的每个方法生成一个从metaClass派生的包装类,该类的invoke方法将直接调用DGM中对应的方法。
优化的具体流程:- 为DefaultGroovyMethods的每个方法生成一个包装类,该类继承GeneratedmetaMethod类,而GeneratedmetaMethod类则继承metaMethod类。该包装类的类名类似于org.codehaus.groovy.runtime.dgm 123 ( 123( 123(后跟一个数),在Groovy分发的jar包中可以找到总共918个这样的类,说明DGM中总共有918个方法。这些包装类的代码形式如下(以上面提到的each方法的包装类为例):
public class dgm$123 extends GeneratedmetaMethod {
...
public Object invoke(Object object, Object[] arguments) {
return DefaultGroovyMethods.each((Object) object, (Closure) arguments[0]); // 将各参数强制转换为对应的类型后,直接调用DefaultGroovyMethods中对应的方法
}
...
}
- 当运行Groovy程序的时候,在metaClassRegistryImpl的实例初始化时,通过调用GeneratedmetaMethod.DgmMethodRecord.loadDgmInfo方法,为所有包装类创建GeneratedmetaMethod.Proxy实例作为其代理
//GeneratedmetaMethod中的静态方法 public static ListloadDgmInfo() throws IOException //GeneratedmetaMethod的子类 public static class Proxy extends GeneratedmetaMethod ;
- 在Groovy程序第一次调用DGM方法时,则由GeneratedmetaMethod.Proxy实例载入对应的包装类并实例化,然后调用其invoke方法。这样就实现了包装类的延迟加载,在一定程度上加快了程序初始化加载的速度。
Groovy通过这种反射的优化方法,即方便了使用,而且生产了Proxy实例,确保了不会产生大量的类,由于每个类都是需要占用内存的,所以会对JVM用于存放类信息的PermGen内存区域造成压力,容易产生OutOfMemoryError。而生产了Proxy实例之后之后,相当于一个使用池子了,每个包装类相当于移动热点,可以被多个使用这个方法的对象使用。



