`
JohnnyJian
  • 浏览: 106065 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

Groovy深入探索——Metaclass的存放

阅读更多
本文介绍了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中。
以上分析有不当之处敬请指出,谢谢大家的阅读。
1
0
分享到:
评论

相关推荐

    Groovy轻松入门——Grails实战基础篇

    ### Groovy轻松入门——Grails实战基础篇 #### 搭建Grails环境及创建Grails Demo程序 **Groovy**是一种面向对象的编程语言,它运行于Java平台上,能够与Java代码无缝集成。而**Grails**则是一款基于Groovy的高性能...

    Groovy need not rails——介绍自己写的一个基于groovy的框架,Webx

    通过深入学习和使用Webx,开发者可以充分利用Groovy的灵活性和生产力优势,同时享受到类似Rails的开发体验,而不必受限于Ruby语言。由于与Java平台的紧密集成,Webx还能够利用丰富的Java生态系统,为大型企业级应用...

    groovy动态方法

    元编程在Groovy中主要通过`MetaClass`接口和`ExpandoMetaClass`类来实现。 `MetaClass`是Groovy中的一个核心概念,它是每个Groovy对象的元数据容器,存储了对象的方法、属性以及它们的调用规则。通过`MetaClass`,...

    Groovy MOP

    1. **MetaClass**: MetaClass是Groovy MOP的核心,它是任何Groovy对象的元数据容器。MetaClass存储了对象的方法、属性以及如何调用这些方法和属性的信息。你可以为任何对象或类动态地替换其MetaClass,从而改变其...

    深入探索Groovy脚本:文件操作的艺术

    本文将深入探讨如何使用Groovy脚本进行文件操作,包括文件的创建、读取、写入、删除等基本操作,以及更高级的操作,如文件过滤和搜索。通过实际的代码示例,我们将展示Groovy在文件操作中的优雅和力量。 Groovy提供...

    Show Your ToolBox——锋利的groovy

    《Show Your ToolBox——锋利的Groovy》 在IT领域,工具的选择和使用往往直接影响到工作效率和项目质量。本文将深入探讨Groovy这门强大的动态编程语言,它以其灵活性和与Java的紧密集成,成为了许多开发者的得力...

    Groovy Script 入门

    static def metaClass = [getProp: { -&gt; prop }] def setProp = { value -&gt; prop = value } } def obj = new MyClass() obj.setProp("Hello") println obj.getProp() // 输出 Hello ``` 1. **定义元类**:使用...

    groovy-docs-1.8.9.zip

    7. **GroovyShell和MetaClass**:Groovy的MetaClass机制允许在运行时动态扩展类的行为。1.8.9版本的文档将详细解释如何使用MetaClass来增强Java对象。 8. **AST(抽象语法树)转换**:Groovy允许开发者编写AST转换...

    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版本,希望大家多多下载,apache-groovy-3.0.8.zip apache官网的groovy3.0.8版本,希望...

    Java调用Groovy,实时动态加载数据库groovy脚本

    3. 加载并执行Groovy脚本:通过GroovyClassLoader的`parseClass()`方法解析Groovy源码,然后使用`newInstance()`方法创建脚本实例,最后通过`invokeMethod()`方法执行脚本中的方法。 在Java与MongoDB的交互中,我们...

    groovy api 资料 自学文档

    - `MetaClass`:Groovy的元编程允许在运行时修改类的行为,通过`metaClass`可以添加方法、属性等。 - 动态属性:Groovy对象可以随时添加或删除属性,无需预先定义。 6. **Groovy脚本和命令行执行**: - ...

    Java中使用Groovy的三种方式

    本文将深入探讨在Java项目中使用Groovy的三种主要方式,并阐述它们各自的优势和应用场景。 一、作为嵌入式脚本 Java 6引入了JSR 223(Scripting for the Java Platform),允许在Java程序中直接执行脚本语言。...

    Groovy反射机制实例

    Groovy反射机制的一个小例子,希望对学习者有用

    Groovy学习笔记.pdf

    Person.metaClass.greet = { -&gt; "Hello, ${delegate.name}!" } Person person = new Person(name: 'Alice') person.greet() // 输出 "Hello, Alice!" ``` 在Groovy中,列表和映射的使用也非常方便。它们的语法简洁...

    eclipse安装groovy插件的步骤

    如果在新建项目选项中出现了与 Groovy 相关的选择项,如 “Groovy Project” 或 “Groovy Class”,则说明 Groovy 插件已成功安装。 #### 二、Eclipse 中 Groovy 的入门使用 接下来,我们来详细介绍如何在 Eclipse...

    最新 groovy开发包

    在这个最新的Groovy开发包中,包含了丰富的学习资源和源码,这将对深入理解和掌握Groovy语言提供极大的帮助。 1. **Groovy基础知识**:Groovy语法简洁,支持面向对象编程、函数式编程和元编程。它的动态类型系统...

    apache-groovy-sdk-2.4.15

    4. **bin**: 存放Groovy的命令行工具,如`groovysh`(交互式Groovy shell)和`groovyConsole`(Groovy脚本编辑和测试环境)。 5. **lib**: 包含Groovy编译器和其他相关库,如Ant任务(groovy-ant)、 Grape(依赖...

    Groovy学习资料

    对于初学者,理解Groovy的Meta-Object Protocol (MOP) 和Groovy的类别增强机制也至关重要。MOP允许你在运行时操作和扩展任何对象的行为,而类别增强则允许在已有类上添加方法或属性,这在不修改源码的情况下增强已有...

Global site tag (gtag.js) - Google Analytics