Javassist入門手冊(cè).docx_第1頁
Javassist入門手冊(cè).docx_第2頁
Javassist入門手冊(cè).docx_第3頁
Javassist入門手冊(cè).docx_第4頁
Javassist入門手冊(cè).docx_第5頁
已閱讀5頁,還剩47頁未讀 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)

文檔簡(jiǎn)介

Javassist入門手冊(cè)Author : Shigeru Chiba Translator : 呂承綱 1. 讀寫字節(jié)碼Javassist是一個(gè)Java字節(jié)碼操作類庫, Java字節(jié)碼被保存在一個(gè)被稱為class文件的二進(jìn)制文件中, 每個(gè)類文件都包含一個(gè)Java類或接口。 Javassist.CtClass是類文件的抽象代表。一個(gè)CtClass(編譯時(shí)類)對(duì)象負(fù)責(zé)處理一個(gè)類文件 。下面是個(gè)簡(jiǎn)單的例子: ClassPool pool = ClassPool.getDefault();CtClass cc = pool.get(test.Rectangle);cc.setSuperclass(pool.get(test.Point);cc.writeFile();程序首先獲取一個(gè)ClassPool對(duì)象,此對(duì)象通過Javassist控制字節(jié)碼的修改。ClassPool對(duì)象是代表類文件的CtClass對(duì) 象的容器。它讀取類文件來構(gòu)建CtClass對(duì)象,并且記錄對(duì)象結(jié)構(gòu),以便于后面的訪問。要修改一個(gè)類的定義,用于必須首先通過ClassPool的get()方法來得到代表這個(gè)類的CtClass對(duì)象。如上所述,我們從ClassPool對(duì)象中獲取代表類test.Rectangle的CtClass對(duì)象,并賦值給變量cc。getDefault()方法用于搜索默認(rèn)的系統(tǒng)路徑并返回ClassPool對(duì)象。 從實(shí)現(xiàn)的角度看,ClassPool就是CtClass對(duì)象的哈希表,以類名稱作為鍵值。ClassPool的get()方法通過指定的鍵值來搜尋CtClass對(duì)象。 通過ClassPool獲取到的CtClass對(duì)象可被修改(后面將展示如何修改CtClass)。在上面的例子中,類test.Rectangle的父類被修改為test.Point。這個(gè)變化將會(huì)通過CtClass的writeFile()方法調(diào)用最終實(shí)現(xiàn)。 writeFile()方法將CtClass對(duì)象轉(zhuǎn)化為類文件并寫入磁盤中。另外,Javassist還提供了一個(gè)直接獲取和修改字節(jié)碼的方法 toBytecode(): byte b = cc.toBytecode();你也可以直接加載CtClass: Class clazz = cc.toClass();toClass()方法會(huì)要求類加載器的當(dāng)前線程來加載代表CtClass的類文件,并返回一個(gè)代表加載類的java.lang.Class對(duì)象。更多詳情,請(qǐng)見本章的下面說明。 定義一個(gè)新類要定義一個(gè)新類,請(qǐng)使用ClssPool的 makeClass()方法。 ClassPool pool = ClassPool.getDefault();CtClass cc = pool.makeClass(Point);上面的代碼定義了一個(gè)沒有任何成員的Point類。Point的成員方法可以通過CtNewMethod的工廠方法創(chuàng)建出來并通過CtClass的addMethod()方法添加到Point類中。 makeClass()方法不能創(chuàng)建一個(gè)新的接口,創(chuàng)建接口要使用ClassPool的makeInterface()方法。接口方法可以通過CtNewMethod的abstractMethod()方法創(chuàng)建。請(qǐng)注意接口方法是抽象的。 凍結(jié)類如果一個(gè)CtClass對(duì)象通過 writeFile(), toClass(), 或toBytecode() 方法被轉(zhuǎn)換為類文件,Javassist就凍結(jié)了此對(duì)象。對(duì)此CtClass對(duì)象的后續(xù)修改都是不允許的。這是為了警告那些試圖修改已經(jīng)被加載的類文件的開發(fā)者,因?yàn)镴VM不允許再次加載同一個(gè)類。 一個(gè)凍結(jié)的CtClass對(duì)象可以被解凍,這樣類定義的修改就被允許。例如: CtClasss cc = .; :cc.writeFile();cc.defrost();cc.setSuperclass(.); / OK since the class is not frozen.執(zhí)行 defrost()方法后,CtClass對(duì)象就可再次被修改。 如果 ClassPool.doPruning()方法設(shè)置為true,Javassist可以優(yōu)化調(diào)整一個(gè)被凍結(jié)的CtClass對(duì)象的數(shù)據(jù)結(jié)構(gòu)。優(yōu)化調(diào)整指的是為了減少內(nèi)存使用,去除對(duì)象內(nèi)的一些不必要的屬性(比如attribute_info,方法體中的Code_attribute)。因此,當(dāng)一個(gè)CtClass對(duì)象被優(yōu)化調(diào)整后,一個(gè)方法的字節(jié)碼除了方法名,方法簽名和注解外都是不可訪問的。優(yōu)化后的CtClass對(duì)象不能被再次解凍。ClassPool.doPruning()方法默認(rèn)值為false。 對(duì)一個(gè)CtClass對(duì)象上執(zhí)行 stopPruning()方法,可防止其優(yōu)化調(diào)整: CtClasss cc = .;cc.stopPruning(true); :cc.writeFile(); / convert to a class file./ cc is not pruned.CtClass對(duì)象cc不會(huì)被優(yōu)化。這樣,在調(diào)用writeFile()后還可以被解凍。 注意:你可能想在調(diào)試時(shí)暫時(shí)不優(yōu)化并凍結(jié)對(duì)象,以便于將一個(gè)改變了的類文件寫入磁盤中。debugWriteFile()方法可以方便的達(dá)到這個(gè)目的。它會(huì)先停止優(yōu)化調(diào)整,寫入一個(gè)class文件,再解凍這個(gè)對(duì)象,并且再次打開優(yōu)化開關(guān)(如果開始時(shí)是打開優(yōu)化開關(guān)的)。類搜索路徑靜態(tài)方法 ClassPool.getDefault() 返回的缺省ClassPool會(huì)搜索和當(dāng)前JVM相同的搜索的路徑。如果 程序是運(yùn)行在譬如JBoss和Tomcat之類的web應(yīng)用服務(wù)器上,ClassPool對(duì)象可能就找不到用戶自己的類,這是由于web應(yīng)用服務(wù)器除了使 用系統(tǒng)類加載器之外,還使用其他多個(gè)類加載器。在這種情況下,額外的類路徑就需要注冊(cè)到ClassPool中。假定pool是對(duì)ClassPool對(duì)象的 引用: pool.insertClassPath(new ClassClassPath(this.getClass();上面的語句將this對(duì)象對(duì)應(yīng)的類路徑注冊(cè)進(jìn)來。除了使用 this.getClass(),你還可以使用任何Class對(duì)象作為參數(shù)。用于類對(duì)象的類加載路徑就這樣被注冊(cè)進(jìn)來了。 你也可以用目錄名稱作為類搜索路徑。比如,下面的代碼將 /usr/local/javalib 目錄加到了搜索路徑中: ClassPool pool = ClassPool.getDefault();pool.insertClassPath(/usr/local/javalib);搜索路徑不僅可以是目錄,還可以是URL: ClassPool pool = ClassPool.getDefault();ClassPath cp = new URLClassPath(, 80, /java/, org.javassist.);pool.insertClassPath(cp);上面的代碼將 :80/java/ 加入類搜索路徑中。這個(gè)URL只能搜索 org.javassist包下的類。比如,要加載org.javassist.test.Main 這個(gè)類,class文件可以這樣獲?。?:80/java/org/javassist/test/Main.class另外,你還可以通過直接通過字節(jié)碼構(gòu)造的方式來獲取CtClass對(duì)象。要這么做,請(qǐng)使用ByteArrayClassPath()方法。例如: ClassPool cp = ClassPool.getDefault();byte b = a byte array;String name = class name;cp.insertClassPath(new ByteArrayClassPath(name, b);CtClass cc = cp.get(name);代表類文件的CtClass對(duì)象是通過 b 構(gòu)造的。當(dāng)get()方法被調(diào)用時(shí),ClassPool通過給定的ByteArrayClassPath來讀取類文件,這種方式和通過名稱獲取CtClass對(duì)象是相同的。 如果你不知道類的全限定名,你可以使用ClassPool的 makeClass()方法: ClassPool cp = ClassPool.getDefault();InputStream ins = an input stream for reading a class file;CtClass cc = cp.makeClass(ins);makeClass() 方法從給定的輸入流返回CtClass對(duì)象。你可以使用makeClass()方法來快速的將類文件加入到ClassPool對(duì)象中。這對(duì)于大的jar包搜索來說是一種性能優(yōu)化。因?yàn)?ClassPool是按需讀取class文件,這樣會(huì)造成對(duì)jar包內(nèi)每個(gè)文件的重復(fù)搜索。makeClass()能起到優(yōu)化搜索的作用,因?yàn)橥ㄟ^makeClass()方法構(gòu)造的CtClass對(duì)象會(huì)保持在ClassPool對(duì)象中,而與之對(duì)應(yīng)的類文件不會(huì)被讀取。 用戶也可以擴(kuò)展類搜索路徑。你可以定義一個(gè)新的ClassPath接口實(shí)現(xiàn)類,并將其實(shí)例通過insertClassPath() 方法放入ClassPool中。這樣允許非標(biāo)準(zhǔn)資源加入到搜索路徑中。2. ClassPool舉個(gè)例子,假定一個(gè)新的getter()方法被加入到類Point對(duì)應(yīng)的CtClass對(duì)象中。之后,程序需要編譯含有g(shù)etter()方法的Point源碼,并將編譯代碼加入到另一個(gè)類Line。如果代表Point的CtClass丟失的話,編譯器就不能編譯getter()方法,因?yàn)樵嫉念惗x并不包含getter()方法。因此,要正確的編譯一個(gè)方法調(diào)用,ClassPool一定需要包含執(zhí)行期間所有的CtClass對(duì)象。 避免內(nèi)存溢出當(dāng)CtClass對(duì)象很大時(shí)(這種情況很少發(fā)生,因?yàn)镴avassist會(huì)通過各種方式減少內(nèi)存消耗),對(duì)應(yīng)的ClassPool就會(huì)消耗大量?jī)?nèi)存。要避免這種情況發(fā)生,你可以顯式的刪除不必要的CtClass對(duì)象。當(dāng)你調(diào)用CtClass對(duì)象的 detach()方法時(shí),CtClass對(duì)象就會(huì)從ClassPool中刪除掉。例如: CtClass cc = . ;cc.writeFile();cc.detach();當(dāng) detach()方法調(diào)用后,你不能再調(diào)用 CtClass對(duì)象的任何方法。不過,你可以通過ClassPool的get()方法獲取一個(gè)新的實(shí)例。當(dāng)你調(diào)用get()方法時(shí),ClassPool會(huì)再次讀取class文件并創(chuàng)建一個(gè)新的CtClass對(duì)象。 另一種方式是用新的ClassPool替代舊的ClassPool。如果舊的ClassPool被垃圾回收,ClassPool中的CtClass對(duì)象也同樣會(huì)被回收掉。創(chuàng)建新的ClassPool代碼片段如下: ClassPool cp = new ClassPool(true);/ if needed, append an extra search path by appendClassPath()這種方式創(chuàng)建的ClassPool和通過 ClassPool.getDefault() 獲取的ClassPool行為一致。ClassPool.getDefault()只是個(gè)方便使用的單例模式。上述代碼會(huì)創(chuàng)建一個(gè)新的ClassPool對(duì)象。getDefault()方法獲取的ClassPool并沒有特別之處,只是方便使用而已。 new ClassPool(true) 是個(gè)方便的構(gòu)造器,它會(huì)將系統(tǒng)搜索路徑加入到ClassPool對(duì)象中。上述構(gòu)造方法和下面代碼作用一樣: ClassPool cp = new ClassPool();cp.appendSystemPath(); / or append another path by appendClassPath()層疊 ClassPool如果程序是運(yùn)行在web應(yīng)用服務(wù)器上,就會(huì)有可能創(chuàng)建多個(gè)ClassPool實(shí)例;每個(gè)ClassPool對(duì)應(yīng)一個(gè)ClassLoader。程序應(yīng)該通過ClassPool的構(gòu)造器而不是 getDefault()方法來創(chuàng)建ClassPool對(duì)象。 就像 java.lang.ClassLoader,ClassPool之間也存在層疊關(guān)系。比如: ClassPool parent = ClassPool.getDefault();ClassPool child = new ClassPool(parent);child.insertClassPath(./classes);當(dāng) child.get()調(diào)用時(shí),子ClassPool會(huì)首先委派給父ClassPool。當(dāng)父ClassPool沒找到這個(gè)類文件時(shí),子ClassPool才會(huì)在 ./classes 目錄下尋找此類文件。 當(dāng)設(shè)置 child.childFirstLookup為 true時(shí),子ClassPool就會(huì)先于父ClassPool來尋找此類文件。比如: 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.通過改變類名來定義新類一個(gè)新類可被定義為已有類的拷貝。程序如下: ClassPool pool = ClassPool.getDefault();CtClass cc = pool.get(Point);cc.setName(Pair);上面程序首先獲取 Point的CtClass對(duì)象。之后這個(gè)CtClass對(duì)象通過setName()方法調(diào)用被賦予新名稱Pair。從此,CtClass對(duì)象所代表的類類名就從Point變更為 Pair。類定義的其他部分則保持不變。 CtClass的 setName()方法修改了ClassPool對(duì)象的映射記錄。從實(shí)現(xiàn)的角度看,ClassPool對(duì)象是CtClass對(duì)象的哈希表。setName()方法改變了CtClass對(duì)象在此哈希表中的key關(guān)聯(lián)。key從原先的類名變更為新的類名。 因此,當(dāng)ClassPool對(duì)象的 get(Point)方法再次調(diào)用,不會(huì)將CtClass對(duì)象返回給cc變量。ClassPool對(duì)象會(huì)再次讀取 Point.class 文件并重新構(gòu)造一個(gè)新的 Point CtClass對(duì)象,而之前關(guān)聯(lián)Point的CtClass對(duì)象已經(jīng)不存在了。如下: 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.cc1和cc2指向同一個(gè)CtClass對(duì)象cc,而cc3卻不是。注意:當(dāng)cc.setName(Pair)執(zhí)行后,cc和cc1所表示的CtClass對(duì)象就指向了Pair類。 ClassPool對(duì)象中類和CtClass對(duì)象是一種一一映射關(guān)系。Javassist不允許兩個(gè)不同的CtClass對(duì)象指向同一個(gè)類,除非是兩個(gè)獨(dú)立的ClassPool。這是和程序轉(zhuǎn)換保持一致的一個(gè)重要特性。 要生成和通過 ClassPool.getDefault()獲取到的默認(rèn)ClassPool的拷貝,請(qǐng)執(zhí)行如下代碼: ClassPool cp = new ClassPool(true);如果你有兩個(gè)ClassPool對(duì)象,那么在每個(gè)ClassPool中都可以獲取到相同類文件的不同CtClass對(duì)象。你可以修改這些CtClass對(duì)象來生成類的不同版本。 通過重命名凍結(jié)類來定義新的類當(dāng)一個(gè)CtClass對(duì)象通過 writeFile()或toBytecode() 方法變成class文件時(shí),Javassist不允許對(duì)CtClass對(duì)象的后續(xù)修改。因此,當(dāng)代表Point類的CtClass對(duì)象被轉(zhuǎn)換為class文件后,你不能通過執(zhí)行setName()方法來將Point修改為Pair。下面的代碼是錯(cuò)誤的: ClassPool pool = ClassPool.getDefault();CtClass cc = pool.get(Point);cc.writeFile();cc.setName(Pair); / wrong since writeFile() has been called.要規(guī)避這個(gè)限制,你可以執(zhí)行 ClassPool的getAndRename() 方法。比如: ClassPool pool = ClassPool.getDefault();CtClass cc = pool.get(Point);cc.writeFile();CtClass cc2 = pool.getAndRename(Point, Pair);當(dāng)getAndRename()方法執(zhí)行時(shí),ClassPool讀取Point.class文件并生成新的CtClass對(duì)象。并且,在記錄到哈希表之前,它將CtClass對(duì)象名稱從Point修改為Pair。因此,在writeFile()或toBytecode()方法執(zhí)行后, getAndRename()方法是可以被調(diào)用的。3. 類加載器 如果要修改的類能提前知道,那么修改類最方便的途徑就是: 1. 通過調(diào)用ClassPool.get()方法獲取CtClass對(duì)象 2. 修改 3. 對(duì)CtClass對(duì)象通過調(diào)用 writeFile()或toBytecode()方法寫入到class文件中。如果一個(gè)類并不是在加載時(shí)就能確定是否需要修改,那么我們就需要用到類加載器。Javassist可以在加載時(shí)使用類加載器,這樣字節(jié)碼就可以修改了。開發(fā)者可以使用自定義的類加載器,也可以使用Javassist中的類加載器。3.1 CtClass.toClass( ) 方法CtClass提供了一個(gè)方便的 toClass()方法來從線程上下文中加載代表這個(gè)CtClass對(duì)象的類。要調(diào)用這個(gè)方法,調(diào)用者需要擁有恰當(dāng)?shù)臋?quán)限,否則就會(huì)拋出SecurityException異常。 下面代碼展示如何使用 toClass()方法: public class Hello public void say() System.out.println(Hello); public class Test public static void main(String args) throws Exception ClassPool cp = ClassPool.getDefault(); CtClass cc = cp.get(Hello); CtMethod m = cc.getDeclaredMethod(say); m.insertBefore( System.out.println(Hello.say():); ); Class c = cc.toClass(); Hello h = (Hello)c.newInstance(); h.say(); Test.main()在Hello的say()方法前插入了 println()調(diào)用。然后,構(gòu)造了一個(gè)修改了的Hello類并調(diào)用say() 方法。 注意上面的代碼,在執(zhí)行 toClass()方法前,類Hello沒有被加載。如果不是這樣,JVM在 toClass()方法請(qǐng)求加載修改了的Hello前會(huì)加載最初的Hello類,這樣加載修改了的Hello類就會(huì)失敗(拋出LinkageError異常)。比如,如果Test的main()是這樣的: public static void main(String args) throws Exception Hello orig = new Hello(); ClassPool cp = ClassPool.getDefault(); CtClass cc = cp.get(Hello); :第一行中原始的Hello類就加載了,之后再調(diào)用 toClass()方法就會(huì)拋異常,這是由于類加載器不能同時(shí)加載兩個(gè)不同版本的Hello類。 如果程序運(yùn)行在JBoss和Tomcat之類的Web應(yīng)用服務(wù)器上,toClass() 方法使用上下文類加載器可能會(huì)有錯(cuò)。在這種情況下,可能會(huì)拋出ClassCastException異常。要避免這種異常,在調(diào)用toClass()方法時(shí)需要顯式的指定類加載器。比如,如果是個(gè)會(huì)話bean對(duì)象,下面的代碼: CtClass cc = .;Class c = cc.toClass(bean.getClass().getClassLoader();就會(huì)正常工作。你必須在 toClass()方法中指明類加載器。 toClass()只是提供了一種方便的手段。如果你需要更強(qiáng)大的功能,需要實(shí)現(xiàn)你自己的類加載器。 3.2 Java中的類加載器在Java中,多個(gè)類加載器可以共存,并且每個(gè)類加載器有自己的名詞空間。不同的類加載器可以加載具有相同類名稱的不同類文件,加載出來的類是不同的。這種特性允許我們?cè)谕粋€(gè)JVM中運(yùn)行多個(gè)程序,即便這些程序包含具有相同名稱但不同實(shí)現(xiàn)的類。 注意:JVM不允許動(dòng)態(tài)加載類。當(dāng)一個(gè)類被加載后,不能在運(yùn)行時(shí)再加載修改后的類。因此,當(dāng)JVM加載一個(gè)類后,不能再對(duì)這個(gè)類做修改。除非使用JDPA(Java平臺(tái)調(diào)試體系結(jié)構(gòu))才有條件的支持類的重新加載。見 3.6節(jié).如果相同的類文件被兩個(gè)不同的類加載器加載,JVM會(huì)生成兩個(gè)名稱和定義相同的不同類。這兩個(gè)類是不等同的,一個(gè)類的實(shí)例是不能賦值給另一個(gè)類的。這兩個(gè)類之間的轉(zhuǎn)換操作會(huì)拋出 ClassCastException 異常。 例如,下面的代碼會(huì)拋出異常: MyClassLoader myLoader = new MyClassLoader();Class clazz = myLoader.loadClass(Box);Object obj = clazz.newInstance();Box b = (Box)obj; / this always throws ClassCastException.類 Box 被兩個(gè)類加載器加載。假定CL這個(gè)類加載器通過代碼的方式加載類。代碼的方式指的是通過 MyClassLoader,Class, Object,和Box,當(dāng)然 CL也加載了這些類。而obj是myLoader類加載器加載的另一個(gè)Box類。由于變量b和變量obj是Box類不同對(duì)象,因此在最后一行代碼轉(zhuǎn)換時(shí)會(huì)拋出ClassCastException異常。 多個(gè)類加載器形成一個(gè)樹狀的結(jié)構(gòu)。除了bootstrap類加載器外,每個(gè)類加載器都有一個(gè)父類加載器。由于一個(gè)類的加載可被委派給他的父 類加載器,因此類可被你所未指定的類加載器加載。也就是說,我們所指定的加載C的類加載器和實(shí)際加載C的類加載器不同。比如,我們可以稱前者為C的初始加 載器,而后者為C的實(shí)際加載器。 更進(jìn)一步的,如果指定的類加載器CL(初始加載器)將加載C的工作委派給父加載器PL,那么,CL就不會(huì)再去加載C中其他類的引用。CL也不會(huì)是這些類的初始加載器,PL才是這些類的初始加載器。類C中所引用的其他類的加載是由C的實(shí)際加載器加載的。 要理解上面的行為,請(qǐng)看下面的代碼: public class Point / loaded by PL private int x, y; public int getX() return x; :public class Box / the initiator is L but the real loader is PL private Point upperLeft, size; public int getBaseX() return upperLeft.x; :public class Window / loaded by a class loader L private Box box; public int getBaseX() return box.getBaseX(); 假定類Window是由類加載器L加載。Window的初始和實(shí)際類加載器都是L。因?yàn)轭怶indow的定義中有對(duì)Box的引用,因此JVM會(huì)要求 L加載Box。這里,我們假定L將此任務(wù)委派給其父加載器PL。這樣,Box的初始加載器為L(zhǎng),但實(shí)際加載器為PL。在這種情況下,Point的初始加載 器就是PL,而不是L,因?yàn)镻oint的初始加載器和Box的實(shí)際加載器需要一致。這樣,L就不會(huì)要求去加載Point。 下面,我們考慮下上述代碼的稍許改動(dòng)。 public class Point private int x, y; public int getX() return x; :public class Box / the initiator is L but the real loader is PL private Point upperLeft, size; public Point getSize() return size; :public class Window / loaded by a class loader L private Box box; public boolean widthIs(int w) Point p = box.getSize(); return w = p.getX(); 現(xiàn)在,Window的定義中含有對(duì)Point的引用。這種情況下,L會(huì)將Point的加載委派給PL(兩個(gè)不同的類加載器不能加載同一個(gè)類)。 如果L沒有將Point的加載委派給PL,widthIs() 方法就會(huì)拋出ClassCastException異常。由于Box的實(shí)際加載器是PL,那么Box中的Point引用也是PL加載的。這種情況下,getSize()方法返回的Point對(duì)象是由PL加載的,而widthIs()方法中變量p的類型是由L加載的,JVM會(huì)認(rèn)為這是兩個(gè)不同的類型,因此會(huì)拋出類型不匹配異常。 這種特性看起來很別扭,但是很有必要的。如果下面的代碼: Point p = box.getSize();沒有拋出異常,那么Window的開發(fā)者就會(huì)破壞Point對(duì)象的封裝性。比如,如果成員x是PL加載的Point的私有域,那么,類Window就可以通過如下的定義由類加載器L直接訪問Point中的x: public class Point public int x, y; / not private public int getX() return x; :關(guān)于Java類加載器的更多細(xì)節(jié),請(qǐng)閱讀: Sheng Liang and Gilad Bracha, Dynamic Class Loading in the Java Virtual Machine, ACM OOPSLA98, pp.36-44, 1998.3.3 使用 javassist.LoaderJavasssit提供了javassist.Loader類加載器。 這個(gè)類加載器使用javassist.ClassPool對(duì)象來讀取類文件。 比如,javassist.Loader能用來加載被Javassist修改了的類。 import javassist.*;import test.Rectangle;public class Main public static void main(String args) throws Throwable ClassPool pool = ClassPool.getDefault(); Loader cl = new Loader(pool); CtClass ct = pool.get(test.Rectangle); ct.setSuperclass(pool.get(test.Point); Class c = cl.loadClass(test.Rectangle); Object rect = c.newInstance(); : 上面的代碼修改了類 test.Rectangle。其父類被設(shè)置為 test.Point。當(dāng)程序再次加載修改后的類時(shí),會(huì)創(chuàng)建出類test.Rectangle的新的對(duì)象。 你可以通過給javassist.Loader 加一個(gè)監(jiān)聽事件滿足在加載時(shí)修改一個(gè)類。這個(gè)事件在類加載時(shí)被觸發(fā)。事件監(jiān)聽類需要實(shí)現(xiàn)如下的接口: public interface Translator public void start(ClassPool pool) throws NotFoundException, CannotCompileException; public void onLoad(ClassPool pool, String classname) throws NotFoundException, CannotCompileException;start()方法在監(jiān)聽器通過 javassist.Loader的addTranslator()方法加入監(jiān)聽器的時(shí)候被調(diào)用。onLoad() 方法在class. onLoad()之前被調(diào)用,這樣就可以修改一個(gè)加載的類了。 比如,下面的監(jiān)聽器在類加載時(shí)將類訪問權(quán)限修改為public。 public class MyTranslator implements Translator void start(ClassPool pool) throws NotFoundException, CannotCompileException void onLoad(ClassPool pool, String classname) throws NotFoundException, CannotCompileException CtClass cc = pool.get(classname); cc.setModifiers(Modifier.PUBLIC); 注意 onLoad()方法不需要調(diào)用 toBytecode()或writeFile()方法,因?yàn)閖avassist.Loader會(huì)調(diào)用這些方法來獲取類文件。 要運(yùn)行帶有MyTranslator的MyApp對(duì)象,代碼如下: import javassist.*;public class Main2 public static void main(String args) throws Throwable Translator t = new MyTranslator(); ClassPool pool = ClassPool.getDefault(); Loader cl = new Loader(); cl.addTranslator(pool, t); cl.run(MyApp, args); 要運(yùn)行這個(gè)程序,請(qǐng): % java Main2 arg1 arg2.類MyApp和其他類都會(huì)被MyTranslator轉(zhuǎn)換。 請(qǐng)注意類MyApp不能訪問Main2,MyTranslator,和ClassPool,因?yàn)樗鼈兪怯刹煌念惣虞d器加載的。MyApp是由javassist.Loader加載,而Main2是由默認(rèn)的Java類加載器加載。 javassist.Loader和java.lang.ClassLoader搜索類的方式不一致。ClassLoader會(huì)先委派父類加載器進(jìn)行加載,只有在父類加載器不能加載時(shí)才自己加載。而javassist.Loader 在委派給父類加載器加載前會(huì)自己加載,只有在如下的情況下才會(huì)委派: 不能通過ClassPool對(duì)象的get()方法加載,或 類已經(jīng)通過 delegateLoadingOf()方法明確的指明由父類加載器加載。 這種搜索方式允許Javassist在加載時(shí)修改類。如果由于某些原因,加載不到修改的類時(shí),它會(huì)委派給父類加載器。當(dāng)一個(gè)類由父類加載器加載后, 這個(gè)類的其他實(shí)例也會(huì)由父類加載器加載,并且不能再修改?;叵肭懊嬲f到的類C是由C的實(shí)際類加載器加載的。如果你的程序加載一個(gè)修改類失敗了,請(qǐng)確認(rèn)是否 所有的類都是由javassist.Loader加載的。 3.4編寫類加載器一個(gè)使用Javasssit的簡(jiǎn)單類加載器如下: import javassist.*;public class SampleLoader extends ClassLoader /* Call MyApp.main(). */ public static void main(String args) throws Throwable SampleLoader s = new SampleLoader(); Class c = s.loadClass(MyApp); c.getDeclaredMethod(main, new Class String.class ) .invoke(null, new Object args ); private ClassPool pool; public SampleLoader() throws NotFoundException pool = new ClassPool(); pool.insertClassPath(./class); / MyApp.class must be there. /* Finds a specified class. * The bytecode for that class can be modified. */ protected Class findClass(String name) throws ClassNotFoundException try CtClass cc = pool.get(name); / modify the CtClass object here byte b = cc.toBytecode(); return defineClass(name, b, 0, b.length); catch (NotFoundException e) throw new ClassNotFoundException(); catch (IOException e) throw new ClassNotFoundException(); catch (CannotCompileException e) throw new ClassNotFoundException(); 類MyApp是個(gè)應(yīng)用程序。要執(zhí)行這個(gè)程序,請(qǐng)將class文件放入./class目錄下,并確保此目錄不在類搜索路徑中。否則,MyApp.class 將會(huì)由系統(tǒng)默認(rèn)類加載器,也就是SampleLoader的父類加載器加載。./class目錄需要在構(gòu)造函數(shù)中通過insertClassPath()加入。你也可以選擇一個(gè)你喜歡的其他目錄名稱。之后請(qǐng)如下執(zhí)行: % java SampleLoader類加載器會(huì)加載 MyApp (./class/MyApp.class)并執(zhí)行 MyApp.main()方法。 這是使用Javassist最簡(jiǎn)單的方式。不過,如果你要寫一個(gè)復(fù)雜的類加載器,你需要了解更多的Java類加載機(jī)制。比如,上面的代碼將 MyApp和SampleLoader放入兩個(gè)獨(dú)立的名稱空間中,這是由于它們是由不同的類加載器加載的。因此,類MyApp不能直接訪問類 SampleLoader。 3.5 修改系統(tǒng)類諸如象java.lang.String 之類的系統(tǒng)類只能由系統(tǒng)類加載器加載。因此,上面所說的SampleLoader或javassist.Loader不能在加載時(shí)修改系統(tǒng)類。 如果你的程序需要這么做,這些系統(tǒng)類只能被靜態(tài)修改。比如,下面的代碼將一個(gè)新字段hiddenValue加入到j(luò)ava.lang.String中: ClassPool pool = ClassPool.getDefault();CtClass cc = pool.get(java.lang.String);cc.addField(new CtField(CtCType, hiddenValue, cc);cc.writeFile(.);上面的代碼生成了一個(gè)新的文件./java/lang/String.class。 要運(yùn)行String修改了的MyApp程序,請(qǐng)執(zhí)行: % java -Xbootclasspath/p:. MyApp arg1 arg2.假定MyApp如下定義: public class MyApp public static void main(String args) throws Exception System.out.println(String.class.getField(hiddenValue).getName(); 如果修改了的String正確加載,MyApp就會(huì)打印出hiddenValue。 注意:應(yīng)用出于某種目的,使用這種技巧覆蓋了rt.jar 中的系統(tǒng)類,則不能正確部署,因?yàn)檫@違背了Java 2 運(yùn)行環(huán)境字節(jié)碼許可。3.6 運(yùn)行時(shí)重載類如果JVM啟動(dòng)并開啟了JPDA(Java平臺(tái)調(diào)試體系結(jié)構(gòu)),一個(gè)類就可以動(dòng)態(tài)的重載。在JVM加載這個(gè)類后,原有的類就被卸載了而新的類被加 載。也就是說,類可以在運(yùn)行時(shí)動(dòng)態(tài)的修改。但是,新的類需要兼容舊的類。JVM不允許兩個(gè)版本的結(jié)構(gòu)變更。它們必須擁有相同的成員和方法。 Javassist提供了個(gè)好用的類方便運(yùn)行時(shí)類的重載。更多信息,請(qǐng)閱讀API文檔中的javassist.tools.HotSwapper。 4. 反射和自定義CtClass提供了反射方法。Javassist中的反射兼容Java 反射API。CtClass提供了getName(),getSuperclass(),getMethods()等等方法,還提供了類修改方法,允許新增成員,構(gòu)造器和方法。構(gòu)造一個(gè)方法體也是可以的。 方法由CtMethod對(duì)象表示。CtMethod中提供了一些方法修改的方法。如果一個(gè)方法繼承自父類方法,那么代表此方法的CtMethod對(duì)象也指向父類。一個(gè)CtMethod對(duì)象關(guān)聯(lián)方法的聲明。 比如,如果類Point中有個(gè) move()方法,并且子類ColorPoint中沒有覆蓋此方法,那么這兩個(gè)類中的 move() 方法由同一個(gè)CtMethod對(duì)象表示。如果CtMethod對(duì)象修改了,兩個(gè)方法都會(huì)被修改。如果你只想改變ColorPoint中的 move()方法,你必須先在代表ColorPoint的CtMethod對(duì)象上拷貝Point的move()方法。CtMethod對(duì)象的拷貝可以通過CtNewMethod.copy()。 Javassist不允許刪除方法或成員,但可以重命名。因此,當(dāng)一個(gè)方法沒用的時(shí)候,可以通過執(zhí)行s

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。

最新文檔

評(píng)論

0/150

提交評(píng)論