【正文】
類加載器 如果要修改的類能提前知道,那么修改類最方便的途徑就是: 并且,在記錄到哈希表之前,它將CtClass對象名稱從Point修改為Pair。CtClass cc2 = (Point, Pair)。CtClass cc = (Point)。 // wrong since writeFile() has been called.要規(guī)避這個限制,你可以執(zhí)行 ClassPool的getAndRename() 方法。()。下面的代碼是錯誤的: ClassPool pool = ()。 通過重命名凍結類來定義新的類當一個CtClass對象通過 writeFile()或toBytecode() 方法變成class文件時,Javassist不允許對CtClass對象的后續(xù)修改。如果你有兩個ClassPool對象,那么在每個ClassPool中都可以獲取到相同類文件的不同CtClass對象。這是和程序轉(zhuǎn)換保持一致的一個重要特性。 ClassPool對象中類和CtClass對象是一種一一映射關系。 // cc3 is not identical to cc.cc1和cc2指向同一個CtClass對象cc,而cc3卻不是。CtClass cc2 = (Pair)。CtClass cc1 = (Point)。如下: ClassPool pool = ()。 因此,當ClassPool對象的 get(Point)方法再次調(diào)用,不會將CtClass對象返回給cc變量。setName()方法改變了CtClass對象在此哈希表中的key關聯(lián)。 CtClass的 setName()方法修改了ClassPool對象的映射記錄。從此,CtClass對象所代表的類類名就從Point變更為 Pair。上面程序首先獲取 Point的CtClass對象。CtClass cc = (Point)。 // changes the behavior of the child.通過改變類名來定義新類一個新類可被定義為已有類的拷貝。()。比如: ClassPool parent = ()。當父ClassPool沒找到這個類文件時,子ClassPool才會在 ./classes 目錄下尋找此類文件。(./classes)。比如: ClassPool parent = ()。程序應該通過ClassPool的構造器而不是 getDefault()方法來創(chuàng)建ClassPool對象。()。 new ClassPool(true) 是個方便的構造器,它會將系統(tǒng)搜索路徑加入到ClassPool對象中。上述代碼會創(chuàng)建一個新的ClassPool對象。// if needed, append an extra search path by appendClassPath()這種方式創(chuàng)建的ClassPool和通過 () 獲取的ClassPool行為一致。如果舊的ClassPool被垃圾回收,ClassPool中的CtClass對象也同樣會被回收掉。當你調(diào)用get()方法時,ClassPool會再次讀取class文件并創(chuàng)建一個新的CtClass對象。當 detach()方法調(diào)用后,你不能再調(diào)用 CtClass對象的任何方法。()。當你調(diào)用CtClass對象的 detach()方法時,CtClass對象就會從ClassPool中刪除掉。 避免內(nèi)存溢出當CtClass對象很大時(這種情況很少發(fā)生,因為Javassist會通過各種方式減少內(nèi)存消耗),對應的ClassPool就會消耗大量內(nèi)存。如果代表Point的CtClass丟失的話,編譯器就不能編譯getter()方法,因為原始的類定義并不包含getter()方法。2. ClassPool舉個例子,假定一個新的getter()方法被加入到類Point對應的CtClass對象中。你可以定義一個新的ClassPath接口實現(xiàn)類,并將其實例通過insertClassPath() 方法放入ClassPool中。makeClass()能起到優(yōu)化搜索的作用,因為通過makeClass()方法構造的CtClass對象會保持在ClassPool對象中,而與之對應的類文件不會被讀取。這對于大的jar包搜索來說是一種性能優(yōu)化。makeClass() 方法從給定的輸入流返回CtClass對象。InputStream ins = an input stream for reading a class file。當get()方法被調(diào)用時,ClassPool通過給定的ByteArrayClassPath來讀取類文件,這種方式和通過名稱獲取CtClass對象是相同的。CtClass cc = (name)。String name = class name。例如: ClassPool cp = ()。比如, 這個類,class文件可以這樣獲取: :80/java/org/javassist/test/另外,你還可以通過直接通過字節(jié)碼構造的方式來獲取CtClass對象。上面的代碼將 :80/java/ 加入類搜索路徑中。ClassPath cp = new URLClassPath(, 80, /java/, .)。(/usr/local/javalib)。 你也可以用目錄名稱作為類搜索路徑。除了使用 (),你還可以使用任何Class對象作為參數(shù)。假定pool是對ClassPool對象的 引用: (new ClassClassPath(()))。如果 程序是運行在譬如JBoss和Tomcat之類的web應用服務器上,ClassPool對象可能就找不到用戶自己的類,這是由于web應用服務器除了使 用系統(tǒng)類加載器之外,還使用其他多個類加載器。它會先停止優(yōu)化調(diào)整,寫入一個class文件,再解凍這個對象,并且再次打開優(yōu)化開關(如果開始時是打開優(yōu)化開關的)。 注意:你可能想在調(diào)試時暫時不優(yōu)化并凍結對象,以便于將一個改變了的類文件寫入磁盤中。 // convert to a class file.// cc is not pruned.CtClass對象cc不會被優(yōu)化。(true)。()方法默認值為false。因此,當一個CtClass對象被優(yōu)化調(diào)整后,一個方法的字節(jié)碼除了方法名,方法簽名和注解外都是不可訪問的。 如果 ()方法設置為true,Javassist可以優(yōu)化調(diào)整一個被凍結的CtClass對象的數(shù)據(jù)結構。(...)。 :()。 一個凍結的CtClass對象可以被解凍,這樣類定義的修改就被允許。對此CtClass對象的后續(xù)修改都是不允許的。請注意接口方法是抽象的。 makeClass()方法不能創(chuàng)建一個新的接口,創(chuàng)建接口要使用ClassPool的makeInterface()方法。上面的代碼定義了一個沒有任何成員的Point類。 ClassPool pool = ()。更多詳情,請見本章的下面說明。你也可以直接加載CtClass: Class clazz = ()。 writeFile()方法將CtClass對象轉(zhuǎn)化為類文件并寫入磁盤中。在上面的例子中。ClassPool的get()方法通過指定的鍵值來搜尋CtClass對象。getDefault()方法用于搜索默認的系統(tǒng)路徑并返回ClassPool對象。要修改一個類的定義,用于必須首先通過ClassPool的get()方法來得到代表這個類的CtClass對象。ClassPool對象是代表類文件的CtClass對 象的容器。()。CtClass cc = ()。一個CtClass(編譯時類)對象負責處理一個類文件 。Javassist入門手冊Author : Shigeru Chiba Translator : 呂承綱 1. 讀寫字節(jié)碼Javassist是一個Java字節(jié)碼操作類庫, Java字節(jié)碼被保存在一個被稱為class文件的二進制文件中, 每個類文件都包含一個Java類或接口。 。下面是個簡單的例子: ClassPool pool = ()。(())。程序首先獲取一個ClassPool對象,此對象通過Javassist控制字節(jié)碼的修改。它讀取類文件來構建CtClass對象,并且記錄對象結構,以便于后面的訪問。如上所述,并賦值給變量cc。 從實現(xiàn)的角度看,ClassPool就是CtClass對象的哈希表,以類名稱作為鍵值。 通過ClassPool獲取到的CtClass對象可被修改(后面將展示如何修改CtClass)。這個變化將會通過CtClass的writeFile()方法調(diào)用最終實現(xiàn)。另外,Javassist還提供了一個直接獲取和修改字節(jié)碼的方法 toBytecode(): byte[] b = ()。toClass()方法會要求類加載器的當前線程來加載代表CtClass的類文件。 定義一個新類要定義一個新類,請使用ClssPool的 makeClass()方法。CtClass cc = (Point)。Point的成員方法可以通過CtNewMethod的工廠方法創(chuàng)建出來并通過CtClass的addMethod()方法添加到Point類中。接口方法可以通過CtNewMethod的abstractMethod()方法創(chuàng)建。 凍結類如果一個CtClass對象通過 writeFile(), toClass(), 或toBytecode() 方法被轉(zhuǎn)換為類文件,Javassist就凍結了此對象。這是為了警告那些試圖修改已經(jīng)被加載的類文件的開發(fā)者,因為JVM不允許再次加載同一個類。例如: CtClasss cc = ...。()。 // OK since the class is not frozen.執(zhí)行 defrost()方法后,CtClass對象就可再次被修改。優(yōu)化調(diào)整指的是為了減少內(nèi)存使用,去除對象內(nèi)的一些不必要的屬性(比如attribute_info,方法體中的Code_attribute)。優(yōu)化后的CtClass對象不能被再次解凍。 對一個CtClass對象上執(zhí)行 stopPruning()方法,可防止其優(yōu)化調(diào)整: CtClasss cc = ...。 :()。這樣,在調(diào)用writeFile()后還可以被解凍。debugWriteFile()方法可以方便的達到這個目的。類搜索路徑靜態(tài)方法 () 返回的缺省ClassPool會搜索和當前JVM相同的搜索的路徑。在這種情況下,額外的類路徑就需要注冊到ClassPool中。上面的語句將this對象對應的類路徑注冊進來。用于類對象的類加載路徑就這樣被注冊進來了。比如,下面的代碼將 /usr/local/javalib 目錄加到了搜索路徑中: ClassPool pool = ()。搜索路徑不僅可以是目錄,還可以是URL: ClassPool pool = ()。(cp)。這個URL只能搜索 。要這么做,請使用ByteArrayClassPath()方法。byte[] b = a byte array。(new ByteArrayClassPath(name, b))。代表類文件的CtClass對象是通過 b 構造的。 如果你不知道類的全限定名,你可以使用ClassPool的 makeClass()方法: ClassPool cp = ()。CtClass cc = (ins)。你可以使用makeClass()方法來快速的將類文件加入到ClassPool對象中。因為 ClassPool是按需讀取class文件,這樣會造成對jar包內(nèi)每個文件的重復搜索。 用戶也可以擴展類搜索路徑。這樣允許非標準資源加入到搜索路徑中。之后,程序需要編譯含有getter()方法的Point源碼,并將編譯代碼加入到另一個類Line。因此,要正確的編譯一個方法調(diào)用,ClassPool一定需要包含執(zhí)行期間所有的CtClass對象。要避免這種情況發(fā)生,你可以顯式的刪除不必要的CtClass對象。例如: CtClass cc = ... 。()。不過,你可以通過ClassPool的get()方法獲取一個新的實例。 另一種方式是用新的ClassPool替代舊的ClassPool。創(chuàng)建新的ClassPool代碼片段如下: ClassPool cp = new ClassPool(true)。()只是個方便使用的單例模式。getDefault()方法獲取的ClassPool并沒有特別之處,只是方便使用而已。上述構造方法和下面代碼作用一樣: ClassPool cp = new ClassPool()。 // or append another path by appendClassPath()層疊 ClassPool如果程序是運行在web應用服務器上,就會有可能創(chuàng)建多個ClassPool實例;每個ClassPool對應一個ClassLoader。 就像 ,ClassPool之間也存在層疊關系。ClassPool child = new ClassPool(parent)。當 ()調(diào)用時,子ClassPool會首先委派給父ClassPool。 當設置 true時,子ClassPool就會先于父ClassPool來尋找此類文件。ClassPool child = new ClassPool(parent)。 // the same class path as the default one. = true。程序如下: ClassPool pool = ()。(Pair)。之后這個CtClass對象通過setName()方法調(diào)用被賦予新名稱Pair。類定義的其他部分則保持不變。從實現(xiàn)的角度看,ClassPoo