本文介绍了Metaclass在Groovy中的存放方式,并对不同的情况(Per-class Metaclass、POGO Per-instance Metaclass和POJO Per-instance Metaclass)进行了分析。
注:以下分析的Groovy源代码来自Groovy 1.7.1,所有测试代码在Groovy 1.7.1下测试通过。
Metaclass
用过Groovy的程序员都或多或少、直接或间接的接触过Metaclass。简单来说,Metaclass就是Class的Class,Class定义了该类实例的行为,Metaclass则定义了该类及其实例的行为(
http://en.wikipedia.org/wiki/Metaclass)。Groovy通过Metaclass使程序可以在运行时修改/添加类的方法、属性等。
Per-class Metaclass
在Groovy中,每个Class都有一个对应的Metaclass,通过这个Metaclass可以给这个Class添加方法或属性:
// 给String类添加了一个名为capitalize的方法
String.metaClass.capitalize = { -> delegate[0].toUpperCase() + delegate[1..-1] }
// 给String类添加了一个名为spaceCount的只读属性
String.metaClass.getSpaceCount = { -> delegate.count(' ') }
assert "this is groovy".capitalize() == "This is groovy"
assert "this is not ruby".spaceCount == 3
除此之外,还可以替换Class对应的Metaclass:
def newMetaClass = new ExpandoMetaClass(Integer)
newMetaClass.initialize()
// 替换Integer类的Metaclass
Integer.metaClass = newMetaClass
assert Integer.metaClass == newMetaClass
但是,Class对应的Metaclass到底存放在哪里呢?
我们可以知道,Java的Class类中是没有存放Metaclass的属性的,而Groovy中的Class类就是Java中的Class类。那么,Groovy就需要一个全局的Map来存放每个Class对应的Metaclass了,其中每个Entry的key是Class,value则是Metaclass。这个全局的Map不需要对key(Class)进行排序,所以可以使用HashMap;应该通过是否是同一个实例来判断key的相等性,所以应该是一个IdentityHashMap;不应该妨碍Class被回收,而且Class被回收时对应的Metaclass也应该被回收,所以应该是一个WeakHashMap;可能被多个线程同时使用,所以应该是一个ConcurrentHashMap。总的来说,这个全局的Map应该是一个WeakIdentityConcurrentHashMap。
在Groovy中,这个全局的Map其实就是groovy.lang.MetaClassRegistry,不过在实现细节上有所区别(或者说更加复杂)。MetaClassRegistry是一个interface,它在Groovy中的唯一实现是org.codehaus.groovy.runtime.metaclass.MetaClassRegistryImpl。我们主要来看MetaClassRegistryImpl中的getMetaClass方法,这个方法用于查找Class对应的Metaclass。
public final MetaClass getMetaClass(Class theClass) {
return ClassInfo.getClassInfo(theClass).getMetaClass();
}
我们再来看看ClassInfo的getClassInfo静态方法:
public static ClassInfo getClassInfo(Class cls) {
// localMapRef的类型是WeakReference<ThreadLocalMapHandler>
ThreadLocalMapHandler handler = localMapRef.get();
SoftReference<LocalMap> ref=null;
if (handler!=null) ref = handler.get();
LocalMap map=null;
if (ref!=null) map = ref.get();
if (map!=null) return map.get(cls);
// 只有当localMapRef或ref已被回收时,才会调用下面的代码
// globalClassSet的类型是ClassInfoSet
return (ClassInfo) globalClassSet.getOrPut(cls,null);
}
ClassInfo中使用静态的globalClassSet存储Metaclass,不过并不是直接存储Class到Metaclass的映射,而是Class到ClassInfo的映射,而ClassInfo则包含了Groovy中跟Class相关的内部属性,其中就包括了Metaclass。
globalClassSet的类型是ClassInfoSet,而ClassInfoSet类继承了ManagedConcurrentMap<Class,ClassInfo>类型,而ManagedConcurrentMap其实就是Weak(Soft)IdentityConcurrentHashMap。限于篇幅,我们在这里就不分析ManagedConcurrentMap和ClassInfoSet的代码了。
再回到ClassInfo的getClassInfo方法,其中第3到8行实现了一个基于ThreadLocal的两级cache。我们先来看第5行的“handler.get()”,这里调用了ThreadLocalMapHandler的get方法:
private static class ThreadLocalMapHandler extends ThreadLocal<SoftReference<LocalMap>> {
SoftReference<LocalMap> recentThreadMapRef; // 最近一次使用的引用
...
public SoftReference<LocalMap> get() {
SoftReference<LocalMap> mapRef = recentThreadMapRef;
LocalMap recent = null;
if (mapRef!=null) recent = mapRef.get();
// 如果最近一次使用的引用就是由当前进程创建的,则直接返回该引用,否则才调用ThreadLocal的get方法。这样可以减少在ThreadLocal.get()中查找Map的消耗
if (recent != null && recent.myThread.get() == Thread.currentThread()) {
return mapRef;
} else {
SoftReference<LocalMap> ref = super.get();
recentThreadMapRef = ref; // 更新最近一次使用的引用
return ref;
}
}
}
再来看ClassInfo.getClassInfo(Class)中第8行的“map.get(cls)”,这里调用了LocalMap的get方法:
private static final class LocalMap extends HashMap<Class,ClassInfo> { // LocalMap本身就是二级cache
private static final int CACHE_SIZE = 5;
...
private final ClassInfo[] cache = new ClassInfo[CACHE_SIZE]; // 这是大小为5的一级cache
...
public ClassInfo get(Class key) {
ClassInfo info = getFromCache(key); // 先在一级cache中查找
if (info != null)
return info;
info = super.get(key); // 再在二级cache中查找
if (info != null)
return putToCache(info); // 写入一级cache
// 如果在两级cache中都找不到,则在globalClassSet中查找,再将结果写入一级cache
return putToCache((ClassInfo) globalClassSet.getOrPut(key,null));
}
...
}
注意,这里并没有将任何的结果写入到二级cache中,因此二级cache永远是空的。我认为这可能是被遗漏了,也可能是发现了二级cache占用了大量内存(或者效果并不明显),所以将写入二级cache的语句去掉了,但是忘了去掉在二级cache中查找的语句。
最后,在MetaClassRegistryImpl.getMetaClass(Class)方法中,查找到Class对应的ClassInfo后,再调用ClassInfo的getMetaClass方法获得Class对应的Metaclass。但是ClassInfo.getMetaClass()并不是简单的返回Metaclass,其中还分为对Metaclass的强引用和弱引用两种情况。由于这已经超出了本文讨论的范围,因此不再深入,对此有兴趣的读者可阅读分析相关代码。
POGO Per-instance Metaclass
POGO的全称是Plain Old Groovy Object,一般指的就是用Groovy编写的对象。每个POGO实例都有一个对应的Metaclass,默认情况下该Metaclass与类的Metaclass相同:
class POGO {} // 这是一个用Groovy编写的对象
def pogo = new POGO()
assert pogo.metaClass == POGO.metaClass // 默认情况下POGO实例的Metaclass与类的Metaclass相同
pogo.metaClass.hello = { -> println 'Hello' }
assert pogo.metaClass != POGO.metaClass // 修改POGO实例的Metaclass后,该POGO实例将拥有独立的Metaclass
我们通过groovyc编译上面的脚本,再通过javap反汇编POGO类,最后手工反编译字节码,可以得到以下代码:
public class POGO implements GroovyObject { // 用Groovy编写的类都实现了GroovyObject接口
...
private transient MetaClass metaClass; // 用于存储实例对应的Metaclass
...
public MetaClass getMetaClass() { // 实现了GroovyObject.getMetaClass()方法
if (metaClass != null)
return metaClass;
metaClass = $getStaticMetaClass(); // 默认情况下返回类的Metaclass
return metaClass;
}
public void setMetaClass(MetaClass metaClass) { // 实现了GroovyObject.setMetaClass(MetaClass)方法
this.metaClass = metaClass;
}
protected MetaClass $getStaticMetaClass() {
ClassInfo classinfo = $staticClassInfo;
if (classinfo == null)
$staticClassInfo = classinfo = ClassInfo.getClassInfo(getClass());
return classinfo.getMetaClass();
}
...
}
容易看出,POGO都实现了GroovyObject接口,而该接口中的getMetaClass和setMetaClass方法分别负责实例对应的Metaclass的读和写。而POGO实例对应的Metaclass则直接存放在实例中的metaClass字段中。
POJO Per-instance Metaclass
与POGO类似的,POJO一般指的就是用Java编写的对象。自Groovy 1.6开始,可以为每个POJO实例设置不同的Metaclass了:
def s1 = "this is groovy"
def s2 = "this is not ruby"
assert s1.size() == 14
assert s2.size() == 16
s1.metaClass.size = { -> 10 } // 只修改s1的Metaclass
assert s1.size() == 10
assert s2.size() == 16
与Per-class Metaclass的情况一样(其实Class也是一个Java类),POJO中并没有存放Metaclass的属性,所以需要用一个(或每个类一个)WeakIdentityConcurrentHashMap来存放Object到Metaclass的映射关系。
POJO Per-instance Metaclass是通过MetaClassRegistryImpl的getMetaClass(Object)和setMetaClass(Object, MetaClass)方法实现读和写的,但是这两个方法并没有加入到MetaClassRegistry接口中(可能是为了保持兼容性)。我们主要看一下MetaClassRegistryImpl.getMetaClass(Object)方法:
public MetaClass getMetaClass(Object obj) {
return ClassInfo.getClassInfo(obj.getClass()).getMetaClass(obj);
}
跟getMetaClass(Class)方法一样,先通过ClassInfo.getClassInfo(Class)方法查找ClassInfo实例。接着调用了ClassInfo.getMetaClass(Object)方法,我们来看看这个方法:
public MetaClass getMetaClass(Object obj) {
final MetaClass instanceMetaClass = getPerInstanceMetaClass(obj);
if (instanceMetaClass != null)
return instanceMetaClass;
// 如果没有为该对象设置Metaclass,则返回类的Metaclass,即默认的Metaclass就是类的Metaclass
lock();
try {
return getMetaClassUnderLock();
} finally {
unlock();
}
}
public MetaClass getPerInstanceMetaClass(Object obj) {
if (perInstanceMetaClassMap == null)
return null;
return (MetaClass) perInstanceMetaClassMap.get(obj);
}
getMetaClass(Object)方法先从perInstanceMetaClassMap属性中查找obj对应的Metaclass,而perInstanceMetaClassMap属性的类型是ManagedConcurrentMap,没错,就是我们上面提到过的Weak(Soft)IdentityConcurrentHashMap的实现。
也就是说,POJO对应的Metaclass是存放在它的Class对应的ClassInfo中的一个ManagedConcurrentMap中的。
总结
总的来说,在各种情况下,Metaclass的存放方式如下:
- Per-class Metaclass:存放在Class对应的ClassInfo中,而Class到ClassInfo的映射关系则存放在ClassInfo中的一个静态的ManagedConcurrentMap中;
- POGO Per-instance Metaclass:直接存放在对象的metaClass字段中。
- POJO Per-instance Metaclass:对象到Metaclass的映射关系存放在该对象的Class对应的ClassInfo中的一个ManagedConcurrentMap中。
以上分析有不当之处敬请指出,谢谢大家的阅读。
分享到:
相关推荐
0,下载Grails( http://dist.codehaus.org/grails/grails-bin-0.4.2.zip ,请留意朝花夕拾——Groovy & Grails中的“最新版本”提示)并解压到自己指定位置(我的位置是D:\D\MY_DEV\grails) 1,设置环境变量GRAILS...
NULL 博文链接:https://key232323.iteye.com/blog/1336400
groovy环境的mock工具,用于辅助单元测试.
赠送jar包:groovy-3.0.9.jar; 赠送原API文档:groovy-3.0.9-javadoc.jar; 赠送源代码:groovy-3.0.9-sources.jar; 赠送Maven依赖信息文件:groovy-3.0.9.pom; 包含翻译后的API文档:groovy-3.0.9-javadoc-API...
Groovy 调用 Java 类groovy 调用 Java class 十分方便,只需要在类前导入该 Java 类,在 Groovy 代码中就可以无缝使用该
apache-groovy-3.0.8.zip apache官网的groovy3.0.8版本,希望大家多多下载,apache-groovy-3.0.8.zip apache官网的groovy3.0.8版本,希望大家多多下载,apache-groovy-3.0.8.zip apache官网的groovy3.0.8版本,希望...
问题原因分析:使用ScriptEngine.eval每次都会对脚本进行编译,生成一个新的类,被GroovyClassLoader加载,大量执行计算后,将导致被加载的类数量不断增加,最终OOM。 解决办法:对计算的表达式expression进行预...
Making Java Groovy is a practical handbook for developers who want to blend Groovy into their day to day work with Java It starts by introducing the key differences between Java and Groovy and how you...
groovy入门经典,groovyeclipse 插件
Java调用Groovy,实时动态加载数据库groovy脚本,java读取mongoDB的groovy脚本,加载实时运行,热部署
Groovy轻松入门—搭建Groovy开发环境 Groovy轻松入门—搭建Groovy开发环境
groovy-2.3.6-installer windows安装版本
什么是 Groovy? Groovy 是 JVM 的一个替代语言 — 替代 是指可以用 Groovy 在 Java 平台上进行 Java 编程,使用方式基本与使用 Java 代码的方式相同。在编写新应用程序时,Groovy 代码能够与 Java 代码很好地结合,...
Groovy in Action, Second Edition is the undisputed definitive reference on the Groovy language. Written by core members of the Groovy language team, this book presents Groovy like no other can—from ...
Groovy
groovy
groovy eclipse plugin2groovy eclipse plugin2groovy eclipse plugin2groovy eclipse plugin2groovy eclipse plugin2groovy eclipse plugin2groovy eclipse plugin2
Groovy jar包 3.0.
Eclipse Groovy插件 Eclipse Groovy插件 Eclipse Groovy插件 Eclipse Groovy插件