ClassPool詳解

轉自:http://www.tuicool.com/articles/nQNn6z

1.簡介


ClassPool對象是一個CtClass對象的容器。一個CtClass對象被構建後,它被記錄在ClassPool中。這是因爲當編譯的原文件關聯到CtClass表示的類, 編譯器要訪問CtClass對象。

例如,假定一個新方法getter()要加入到CtClass對象表示的的Point類。程序試圖編譯Point中的方法getter()的源代碼,用編譯過的代碼做爲方法內容,將它加到另一個類Line中。如果CtClass對象表示的Point丟失了,編譯器將不能編譯getter()方法。註冊初始的類不包含getter()方法。因此,爲了正確的編譯一個方法,ClassPool必須擁有程序運行時所有的CtClass實例。

2. 免內存溢出


ClassPool的特點決定了當 CtClass對象數量很多時,它所佔的內存會非常大。爲了避免這種情況發生,你可以明確的移除一個ClassPool中的不需要的CtClass對象。如果用CtClass的detach()方法,CtClass對象將從ClassPool中移除。例如:

<span style="font-size:18px;">cc.detach();</span>
執行deatach()後,將不能執行CtClass對象的任何方法。但是,可以執行ClassPool的get()方法得到表示同一個類的CtClass實例。執行get()後,ClassPool再次讀取類文件,重新建立CtClass對象。

另一個辦法是用新的ClassPool替換老的。如果老的 ClassPool當做垃圾被回收了,它裏面的CtClass對象也會被回收,建立新的ClassPool實例,執行下面的代碼片段:


<span style="font-size:18px;">ClassPool cp = new ClassPool(true);
// if needed, append an extra search path by appendClassPath()</span>

通過ClassPool.getDefault()構建默認行爲的ClassPool對象。注意ClassPool.getDefault()是爲了方便提供的單子工廠方法。它保持着單獨的對象並重用它。getDefault()返回的ClassPool對象沒有特別的作用。getDefault()是一個方便的方法。

注意 new ClassPool(true)是個方便的構造器,它構建一個ClassPool對象並加入系統搜索路徑。執行它等同於下面的代碼:


<span style="font-size:18px;">ClassPool cp = new ClassPool();
cp.appendSystemPath(); // or append another path by appendClassPath()</span>

3. 級聯的ClassPools


如果程序運行在web服務器中,可能需要建立多個ClassPool;要爲每個類載入器建一個ClassPool。程序將不用getDefault(),用ClassPool構造器建一個ClassPool對象。多個CLassPool對象像java.lang.ClassLoader一樣級聯起來。例如,

<span style="font-size:18px;">ClassPool parent = ClassPool.getDefault();
ClassPool child = new ClassPool(parent);
child.insertClassPath("./classes");</span>

如果執行child.get(),孩子ClassPool首先去查找父ClassPool。如果說父查找失敗,孩子再去./classes目錄下查找類。
如果child.childFirstLookup爲true,先在孩子中查找,再到父中查。例如:


<span style="font-size:18px;">ClassPool parent = ClassPool.getDefault();
ClassPool child = new ClassPool(parent);
child.appendSystemPath();// the same class path as the default one.
child.childFirstLookup = true; // changes the behavior of the child.</span>


4. 改變類名,定義一個新類


一個新類可以被定義爲已經存在類的拷貝。看下面的程序:

<span style="font-size:18px;">ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.get("Point");
cc.setName("Pair");</span>
這個程序首先得到Point作爲CtClass對象,然後用setName()爲CtClass設置了新的名字Pair,之後CtClass對象的類名被改爲Pari,類定義的其他部分沒有變。 注意 CtClass的setName()改變了ClassPool中的一個記錄。從實現的角度 ,setName()改變的是ClassPool 的hash表中的CtClass對象關聯的key。Key從初始的類名改到了新類名。
因此,如果get(“Point”) 再次被執行後,將不能返回cc引用的CtClass對象。ClassPool再次讀取Point.class類文件,爲Point構建新的CtClass對象。這是因爲Point命名的CtClass對象已經不存在了。看下面例子:

<span style="font-size:18px;">ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.get("Point");
CtClass cc1 = pool.get("Point"); // cc1 is identical to cc.
cc.setName("Pair");
CtClass cc2 = pool.get("Pair"); // cc2 is identical to cc.
CtClass cc3 = pool.get("Point"); // cc3 is not identical to cc.</span>

Cc1和cc2引用同一個CtClass實例cc,而cc3不是。注意,cc.setName(“Pair”)執行後,CtClass對象Pair 被cc和cc1引用。
ClassPool對象用來維持類和CtClass對象之間一對一的關係映射。Javassist不允許用兩個有區別的CtClass對象表示同一個類,除非建兩個單獨CLassPool。這是對程序轉換前後有意義的功能。

建立另一個ClassPool默認實例的拷貝,用ClassPool.getDefault()得到,執行下面代碼:

ClassPool cp = new ClassPool(true);如果有兩個ClassPool對象,可以分別從ClassPool中得到表示同一個類文件的有區別的CtClass對象。可以編輯不同的CtClass對象用來生成不同的類的版本。 改變凍結類名,定義一個新類
當CtClass對象通過writeFile()或toBytecode()轉換爲類文件,Javassist將拒絕CtClass對象的修改。因此CtClass表示的Point類是被轉換到類文件中了,執行setName()不能定義Pair類作爲Point的拷貝,它將被拒絕。下面的代碼是錯誤的:


<span style="font-size:18px;">ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.get("Point");
cc.writeFile();cc.setName("Pair"); // wrong since writeFile() has been called.</span>

爲了避免這個限制,可以執行ClassPool的getAndRename()方法,例如:

<span style="font-size:18px;">ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.get("Point");
cc.writeFile();
CtClass cc2 = pool.getAndRename("Point", "Pair"); </span>

如果執行getAndRename(),ClassPool首先讀取Point.class建立一個新的CtClass。但是,它重命名CtClass從Point改爲Pair以前,要先在CtClass的Hash表中作記錄。因此,writeFile()或toBytecode()執行後可以執行getAndRename()。

發佈了73 篇原創文章 · 獲贊 18 · 訪問量 23萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章