隨筆 - 4, 文章 - 0, 評(píng)論 - 2, 引用 - 0
          數(shù)據(jù)加載中……

          Groovy深入探索——Metaclass的存放

          本文介紹了Metaclass在Groovy中的存放方式,并對(duì)不同的情況(Per-class Metaclass、POGO Per-instance Metaclass和POJO Per-instance Metaclass)進(jìn)行了分析。

          注:以下分析的Groovy源代碼來(lái)自Groovy 1.7.1,所有測(cè)試代碼在Groovy 1.7.1下測(cè)試通過(guò)。

          Metaclass
          用過(guò)Groovy的程序員都或多或少、直接或間接的接觸過(guò)Metaclass。簡(jiǎn)單來(lái)說(shuō),Metaclass就是Class的Class,Class定義了該類實(shí)例的行為,Metaclass則定義了該類及其實(shí)例的行為(http://en.wikipedia.org/wiki/Metaclass)。Groovy通過(guò)Metaclass使程序可以在運(yùn)行時(shí)修改/添加類的方法、屬性等。

          Per-class Metaclass
          在Groovy中,每個(gè)Class都有一個(gè)對(duì)應(yīng)的Metaclass,通過(guò)這個(gè)Metaclass可以給這個(gè)Class添加方法或?qū)傩裕?br />
          1 // 給String類添加了一個(gè)名為capitalize的方法
          2 String.metaClass.capitalize = { -> delegate[0].toUpperCase() + delegate[1..-1] }
          3 // 給String類添加了一個(gè)名為spaceCount的只讀屬性
          4 String.metaClass.getSpaceCount = { -> delegate.count(' ') }
          5 
          6 assert "this is groovy".capitalize() == "This is groovy"
          7 assert "this is not ruby".spaceCount == 3

          除此之外,還可以替換Class對(duì)應(yīng)的Metaclass:
          1 def newMetaClass = new ExpandoMetaClass(Integer)
          2 newMetaClass.initialize()
          3 // 替換Integer類的Metaclass
          4 Integer.metaClass = newMetaClass
          5 assert Integer.metaClass == newMetaClass

          但是,Class對(duì)應(yīng)的Metaclass到底存放在哪里呢?

          我們可以知道,Java的Class類中是沒(méi)有存放Metaclass的屬性的,而Groovy中的Class類就是Java中的Class類。那么,Groovy就需要一個(gè)全局的Map來(lái)存放每個(gè)Class對(duì)應(yīng)的Metaclass了,其中每個(gè)Entry的key是Class,value則是Metaclass。這個(gè)全局的Map不需要對(duì)key(Class)進(jìn)行排序,所以可以使用HashMap;應(yīng)該通過(guò)是否是同一個(gè)實(shí)例來(lái)判斷key的相等性,所以應(yīng)該是一個(gè)IdentityHashMap;不應(yīng)該妨礙Class被回收,而且Class被回收時(shí)對(duì)應(yīng)的Metaclass也應(yīng)該被回收,所以應(yīng)該是一個(gè)WeakHashMap;可能被多個(gè)線程同時(shí)使用,所以應(yīng)該是一個(gè)ConcurrentHashMap。總的來(lái)說(shuō),這個(gè)全局的Map應(yīng)該是一個(gè)WeakIdentityConcurrentHashMap。

          在Groovy中,這個(gè)全局的Map其實(shí)就是groovy.lang.MetaClassRegistry,不過(guò)在實(shí)現(xiàn)細(xì)節(jié)上有所區(qū)別(或者說(shuō)更加復(fù)雜)。MetaClassRegistry是一個(gè)interface,它在Groovy中的唯一實(shí)現(xiàn)是org.codehaus.groovy.runtime.metaclass.MetaClassRegistryImpl。我們主要來(lái)看MetaClassRegistryImpl中的getMetaClass方法,這個(gè)方法用于查找Class對(duì)應(yīng)的Metaclass。
          1 public final MetaClass getMetaClass(Class theClass) {
          2     return ClassInfo.getClassInfo(theClass).getMetaClass();
          3 }

          我們?cè)賮?lái)看看ClassInfo的getClassInfo靜態(tài)方法:
           1 public static ClassInfo getClassInfo(Class cls) {
           2     // localMapRef的類型是WeakReference<ThreadLocalMapHandler>
           3     ThreadLocalMapHandler handler = localMapRef.get();
           4     SoftReference<LocalMap> ref=null;
           5     if (handler!=null) ref = handler.get();
           6     LocalMap map=null;
           7     if (ref!=null) map = ref.get();
           8     if (map!=nullreturn map.get(cls);
           9     // 只有當(dāng)localMapRef或ref已被回收時(shí),才會(huì)調(diào)用下面的代碼
          10     // globalClassSet的類型是ClassInfoSet
          11     return (ClassInfo) globalClassSet.getOrPut(cls,null);
          12 }

          ClassInfo中使用靜態(tài)的globalClassSet存儲(chǔ)Metaclass,不過(guò)并不是直接存儲(chǔ)Class到Metaclass的映射,而是Class到ClassInfo的映射,而ClassInfo則包含了Groovy中跟Class相關(guān)的內(nèi)部屬性,其中就包括了Metaclass。

          globalClassSet的類型是ClassInfoSet,而ClassInfoSet類繼承了ManagedConcurrentMap<Class,ClassInfo>類型,而ManagedConcurrentMap其實(shí)就是Weak(Soft)IdentityConcurrentHashMap。限于篇幅,我們?cè)谶@里就不分析ManagedConcurrentMap和ClassInfoSet的代碼了。

          再回到ClassInfo的getClassInfo方法,其中第3到8行實(shí)現(xiàn)了一個(gè)基于ThreadLocal的兩級(jí)cache。我們先來(lái)看第5行的“handler.get()”,這里調(diào)用了ThreadLocalMapHandler的get方法:
           1 private static class ThreadLocalMapHandler extends ThreadLocal<SoftReference<LocalMap>> {
           2     SoftReference<LocalMap> recentThreadMapRef; // 最近一次使用的引用
           3     
           4     public SoftReference<LocalMap> get() {
           5         SoftReference<LocalMap> mapRef = recentThreadMapRef;
           6         LocalMap recent = null;
           7         if (mapRef!=null) recent = mapRef.get();
           8         // 如果最近一次使用的引用就是由當(dāng)前進(jìn)程創(chuàng)建的,則直接返回該引用,否則才調(diào)用ThreadLocal的get方法。這樣可以減少在ThreadLocal.get()中查找Map的消耗
           9         if (recent != null && recent.myThread.get() == Thread.currentThread()) {
          10             return mapRef;
          11         } else {
          12             SoftReference<LocalMap> ref = super.get();
          13             recentThreadMapRef = ref; // 更新最近一次使用的引用
          14             return ref;
          15         }
          16     }
          17 }

          再來(lái)看ClassInfo.getClassInfo(Class)中第8行的“map.get(cls)”,這里調(diào)用了LocalMap的get方法:
           1 private static final class LocalMap extends HashMap<Class,ClassInfo> { // LocalMap本身就是二級(jí)cache
           2     private static final int CACHE_SIZE = 5;
           3     
           4     private final ClassInfo[] cache = new ClassInfo[CACHE_SIZE]; // 這是大小為5的一級(jí)cache
           5     
           6     public ClassInfo get(Class key) {
           7         ClassInfo info = getFromCache(key); // 先在一級(jí)cache中查找
           8         if (info != null)
           9           return info;
          10 
          11         info = super.get(key); // 再在二級(jí)cache中查找
          12         if (info != null)
          13           return putToCache(info); // 寫(xiě)入一級(jí)cache
          14 
          15         // 如果在兩級(jí)cache中都找不到,則在globalClassSet中查找,再將結(jié)果寫(xiě)入一級(jí)cache
          16         return putToCache((ClassInfo) globalClassSet.getOrPut(key,null));
          17     }
          18     
          19 }

          注意,這里并沒(méi)有將任何的結(jié)果寫(xiě)入到二級(jí)cache中,因此二級(jí)cache永遠(yuǎn)是空的。我認(rèn)為這可能是被遺漏了,也可能是發(fā)現(xiàn)了二級(jí)cache占用了大量?jī)?nèi)存(或者效果并不明顯),所以將寫(xiě)入二級(jí)cache的語(yǔ)句去掉了,但是忘了去掉在二級(jí)cache中查找的語(yǔ)句。

          最后,在MetaClassRegistryImpl.getMetaClass(Class)方法中,查找到Class對(duì)應(yīng)的ClassInfo后,再調(diào)用ClassInfo的getMetaClass方法獲得Class對(duì)應(yīng)的Metaclass。但是ClassInfo.getMetaClass()并不是簡(jiǎn)單的返回Metaclass,其中還分為對(duì)Metaclass的強(qiáng)引用和弱引用兩種情況。由于這已經(jīng)超出了本文討論的范圍,因此不再深入,對(duì)此有興趣的讀者可閱讀分析相關(guān)代碼。

          POGO Per-instance Metaclass
          POGO的全稱是Plain Old Groovy Object,一般指的就是用Groovy編寫(xiě)的對(duì)象。每個(gè)POGO實(shí)例都有一個(gè)對(duì)應(yīng)的Metaclass,默認(rèn)情況下該Metaclass與類的Metaclass相同:
          1 class POGO {} // 這是一個(gè)用Groovy編寫(xiě)的對(duì)象
          2 
          3 def pogo = new POGO()
          4 assert pogo.metaClass == POGO.metaClass // 默認(rèn)情況下POGO實(shí)例的Metaclass與類的Metaclass相同
          5 
          6 pogo.metaClass.hello = { -> println 'Hello' }
          7 assert pogo.metaClass != POGO.metaClass // 修改POGO實(shí)例的Metaclass后,該P(yáng)OGO實(shí)例將擁有獨(dú)立的Metaclass

          我們通過(guò)groovyc編譯上面的腳本,再通過(guò)javap反匯編POGO類,最后手工反編譯字節(jié)碼,可以得到以下代碼:
           1 public class POGO implements GroovyObject { // 用Groovy編寫(xiě)的類都實(shí)現(xiàn)了GroovyObject接口
           2     
           3     private transient MetaClass metaClass; // 用于存儲(chǔ)實(shí)例對(duì)應(yīng)的Metaclass
           4     
           5     public MetaClass getMetaClass() { // 實(shí)現(xiàn)了GroovyObject.getMetaClass()方法
           6         if (metaClass != null)
           7             return metaClass;
           8         metaClass = $getStaticMetaClass(); // 默認(rèn)情況下返回類的Metaclass
           9         return metaClass;
          10     }
          11 
          12     public void setMetaClass(MetaClass metaClass) { // 實(shí)現(xiàn)了GroovyObject.setMetaClass(MetaClass)方法
          13         this.metaClass = metaClass;
          14     }
          15 
          16     protected MetaClass $getStaticMetaClass() {
          17         ClassInfo classinfo = $staticClassInfo;
          18         if (classinfo == null)
          19             $staticClassInfo = classinfo = ClassInfo.getClassInfo(getClass());
          20         return classinfo.getMetaClass();
          21     }
          22     
          23 }

          容易看出,POGO都實(shí)現(xiàn)了GroovyObject接口,而該接口中的getMetaClass和setMetaClass方法分別負(fù)責(zé)實(shí)例對(duì)應(yīng)的Metaclass的讀和寫(xiě)。而POGO實(shí)例對(duì)應(yīng)的Metaclass則直接存放在實(shí)例中的metaClass字段中。

          POJO Per-instance Metaclass
          與POGO類似的,POJO一般指的就是用Java編寫(xiě)的對(duì)象。自Groovy 1.6開(kāi)始,可以為每個(gè)POJO實(shí)例設(shè)置不同的Metaclass了:
          1 def s1 = "this is groovy"
          2 def s2 = "this is not ruby"
          3 assert s1.size() == 14
          4 assert s2.size() == 16
          5 s1.metaClass.size = { -> 10 } // 只修改s1的Metaclass
          6 assert s1.size() == 10
          7 assert s2.size() == 16

          與Per-class Metaclass的情況一樣(其實(shí)Class也是一個(gè)Java類),POJO中并沒(méi)有存放Metaclass的屬性,所以需要用一個(gè)(或每個(gè)類一個(gè))WeakIdentityConcurrentHashMap來(lái)存放Object到Metaclass的映射關(guān)系。

          POJO Per-instance Metaclass是通過(guò)MetaClassRegistryImpl的getMetaClass(Object)和setMetaClass(Object, MetaClass)方法實(shí)現(xiàn)讀和寫(xiě)的,但是這兩個(gè)方法并沒(méi)有加入到MetaClassRegistry接口中(可能是為了保持兼容性)。我們主要看一下MetaClassRegistryImpl.getMetaClass(Object)方法:
          1 public MetaClass getMetaClass(Object obj) {
          2     return ClassInfo.getClassInfo(obj.getClass()).getMetaClass(obj);
          3 }

          跟getMetaClass(Class)方法一樣,先通過(guò)ClassInfo.getClassInfo(Class)方法查找ClassInfo實(shí)例。接著調(diào)用了ClassInfo.getMetaClass(Object)方法,我們來(lái)看看這個(gè)方法:
           1 public MetaClass getMetaClass(Object obj) {
           2     final MetaClass instanceMetaClass = getPerInstanceMetaClass(obj);
           3     if (instanceMetaClass != null)
           4         return instanceMetaClass;
           5 
           6     // 如果沒(méi)有為該對(duì)象設(shè)置Metaclass,則返回類的Metaclass,即默認(rèn)的Metaclass就是類的Metaclass
           7     lock();
           8     try {
           9         return getMetaClassUnderLock();
          10     } finally {
          11         unlock();
          12     }
          13 }
          14 
          15 public MetaClass getPerInstanceMetaClass(Object obj) {
          16     if (perInstanceMetaClassMap == null)
          17       return null;
          18 
          19     return (MetaClass) perInstanceMetaClassMap.get(obj);
          20 }

          getMetaClass(Object)方法先從perInstanceMetaClassMap屬性中查找obj對(duì)應(yīng)的Metaclass,而perInstanceMetaClassMap屬性的類型是ManagedConcurrentMap,沒(méi)錯(cuò),就是我們上面提到過(guò)的Weak(Soft)IdentityConcurrentHashMap的實(shí)現(xiàn)。

          也就是說(shuō),POJO對(duì)應(yīng)的Metaclass是存放在它的Class對(duì)應(yīng)的ClassInfo中的一個(gè)ManagedConcurrentMap中的。

          總結(jié)
          總的來(lái)說(shuō),在各種情況下,Metaclass的存放方式如下:
          • Per-class Metaclass:存放在Class對(duì)應(yīng)的ClassInfo中,而Class到ClassInfo的映射關(guān)系則存放在ClassInfo中的一個(gè)靜態(tài)的ManagedConcurrentMap中;
          • POGO Per-instance Metaclass:直接存放在對(duì)象的metaClass字段中。
          • POJO Per-instance Metaclass:對(duì)象到Metaclass的映射關(guān)系存放在該對(duì)象的Class對(duì)應(yīng)的ClassInfo中的一個(gè)ManagedConcurrentMap中。
          以上分析有不當(dāng)之處敬請(qǐng)指出,謝謝大家的閱讀。

          posted on 2010-03-19 20:02 Johnny Jian 閱讀(3655) 評(píng)論(0)  編輯  收藏 所屬分類: Groovy

          主站蜘蛛池模板: 广汉市| 绥德县| 禹州市| 肥东县| 丰城市| 仪陇县| 遂昌县| 京山县| 黄山市| 五莲县| 和平县| 寿光市| 万盛区| 铜梁县| 桑植县| 湘潭市| 南雄市| 英德市| 上蔡县| 东宁县| 永昌县| 自治县| 义马市| 台东市| 和田市| 郯城县| 宁夏| 阳曲县| 桃园县| 钦州市| 高阳县| 阳西县| 康乐县| 五寨县| 温州市| 蒙阴县| 金乡县| 泗洪县| 黎川县| 宁津县| 瑞安市|