【正文】
對象。你可以修改這些CtClass對象來生成類的不同版本。 通過重命名凍結(jié)類來定義新的類當一個CtClass對象通過 writeFile()或toBytecode() 方法變成class文件時,Javassist不允許對CtClass對象的后續(xù)修改。因此,當代表Point類的CtClass對象被轉(zhuǎn)換為class文件后,你不能通過執(zhí)行setName()方法來將Point修改為Pair。下面的代碼是錯誤的: ClassPool pool = ()。CtClass cc = (Point)。()。(Pair)。 // wrong since writeFile() has been called.要規(guī)避這個限制,你可以執(zhí)行 ClassPool的getAndRename() 方法。比如: ClassPool pool = ()。CtClass cc = (Point)。()。CtClass cc2 = (Point, Pair)。當getAndRename()方法執(zhí)行時。并且,在記錄到哈希表之前,它將CtClass對象名稱從Point修改為Pair。因此,在writeFile()或toBytecode()方法執(zhí)行后, getAndRename()方法是可以被調(diào)用的。3. 類加載器 如果要修改的類能提前知道,那么修改類最方便的途徑就是: 1. ()方法獲取CtClass對象 2. 修改 3. 對CtClass對象通過調(diào)用 writeFile()或toBytecode()方法寫入到class文件中。如果一個類并不是在加載時就能確定是否需要修改,那么我們就需要用到類加載器。Javassist可以在加載時使用類加載器,這樣字節(jié)碼就可以修改了。開發(fā)者可以使用自定義的類加載器,也可以使用Javassist中的類加載器。 ( ) 方法CtClass提供了一個方便的 toClass()方法來從線程上下文中加載代表這個CtClass對象的類。要調(diào)用這個方法,調(diào)用者需要擁有恰當?shù)臋?quán)限,否則就會拋出SecurityException異常。 下面代碼展示如何使用 toClass()方法: public class Hello { public void say() { (Hello)。 }}public class Test { public static void main(String[] args) throws Exception { ClassPool cp = ()。 CtClass cc = (Hello)。 CtMethod m = (say)。 ({ (\():\)。 })。 Class c = ()。 Hello h = (Hello)()。 ()。 }}()在Hello的say()方法前插入了 println()調(diào)用。然后,構(gòu)造了一個修改了的Hello類并調(diào)用say() 方法。 注意上面的代碼,在執(zhí)行 toClass()方法前,類Hello沒有被加載。如果不是這樣,JVM在 toClass()方法請求加載修改了的Hello前會加載最初的Hello類,這樣加載修改了的Hello類就會失?。⊕伋鯨inkageError異常)。比如,如果Test的main()是這樣的: public static void main(String[] args) throws Exception { Hello orig = new Hello()。 ClassPool cp = ()。 CtClass cc = (Hello)。 :}第一行中原始的Hello類就加載了,之后再調(diào)用 toClass()方法就會拋異常,這是由于類加載器不能同時加載兩個不同版本的Hello類。 如果程序運行在JBoss和Tomcat之類的Web應用服務器上,toClass() 方法使用上下文類加載器可能會有錯。在這種情況下,可能會拋出ClassCastException異常。要避免這種異常,在調(diào)用toClass()方法時需要顯式的指定類加載器。比如,如果是個會話bean對象,下面的代碼: CtClass cc = ...。Class c = (().getClassLoader())。就會正常工作。你必須在 toClass()方法中指明類加載器。 toClass()只是提供了一種方便的手段。如果你需要更強大的功能,需要實現(xiàn)你自己的類加載器。 Java中的類加載器在Java中,多個類加載器可以共存,并且每個類加載器有自己的名詞空間。不同的類加載器可以加載具有相同類名稱的不同類文件,加載出來的類是不同的。這種特性允許我們在同一個JVM中運行多個程序,即便這些程序包含具有相同名稱但不同實現(xiàn)的類。 注意:JVM不允許動態(tài)加載類。當一個類被加載后,不能在運行時再加載修改后的類。因此,當JVM加載一個類后,不能再對這個類做修改。除非使用JDPA(Java平臺調(diào)試體系結(jié)構(gòu))才有條件的支持類的重新加載。見 .如果相同的類文件被兩個不同的類加載器加載,JVM會生成兩個名稱和定義相同的不同類。這兩個類是不等同的,一個類的實例是不能賦值給另一個類的。這兩個類之間的轉(zhuǎn)換操作會拋出 ClassCastException 異常。 例如,下面的代碼會拋出異常: MyClassLoader myLoader = new MyClassLoader()。Class clazz = (Box)。Object obj = ()。Box b = (Box)obj。 // this always throws ClassCastException.類 Box 被兩個類加載器加載。假定CL這個類加載器通過代碼的方式加載類。代碼的方式指的是通過 MyClassLoader,Class, Object,和Box,當然 CL也加載了這些類。而obj是myLoader類加載器加載的另一個Box類。由于變量b和變量obj是Box類不同對象,因此在最后一行代碼轉(zhuǎn)換時會拋出ClassCastException異常。 多個類加載器形成一個樹狀的結(jié)構(gòu)。除了bootstrap類加載器外,每個類加載器都有一個父類加載器。由于一個類的加載可被委派給他的父 類加載器,因此類可被你所未指定的類加載器加載。也就是說,我們所指定的加載C的類加載器和實際加載C的類加載器不同。比如,我們可以稱前者為C的初始加 載器,而后者為C的實際加載器。 更進一步的,如果指定的類加載器CL(初始加載器)將加載C的工作委派給父加載器PL,那么,CL就不會再去加載C中其他類的引用。CL也不會是這些類的初始加載器,PL才是這些類的初始加載器。類C中所引用的其他類的加載是由C的實際加載器加載的。 要理解上面的行為,請看下面的代碼: 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 。 } :}public class Window { // loaded by a class loader L private Box box。 public int getBaseX() { return ()。 }}假定類Window是由類加載器L加載。Window的初始和實際類加載器都是L。因為類Window的定義中有對Box的引用,因此JVM會要求 L加載Box。這里,我們假定L將此任務委派給其父加載器PL。這樣,Box的初始加載器為L,但實際加載器為PL。在這種情況下,Point的初始加載 器就是PL,而不是L,因為Point的初始加載器和Box的實際加載器需要一致。這樣,L就不會要求去加載Point。 下面,我們考慮下上述代碼的稍許改動。 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 = ()。 return w == ()。 }}現(xiàn)在,Window的定義中含有對Point的引用。這種情況下,L會將Point的加載委派給PL(兩個不同的類加載器不能加載同一個類)。 如果L沒有將Point的加載委派給PL,widthIs() 方法就會拋出ClassCastException異常。由于Box的實際加載器是PL,那么Box中的Point引用也是PL加載的。這種情況下,getSize()方法返回的Point對象是由PL加載的,而widthIs()方法中變量p的類型是由L加載的,JVM會認為這是兩個不同的類型,因此會拋出類型不匹配異常。 這種特性看起來很別扭,但是很有必要的。如果下面的代碼: Point p = ()。沒有拋出異常,那么Window的開發(fā)者就會破壞Point對象的封裝性。比如,如果成員x是PL加載的Point的私有域,那么,類Window就可以通過如下的定義由類加載器L直接訪問Point中的x: public class Point { public int x, y。 // not private public int getX() { return x。 } :}關(guān)于Java類加載器的更多細節(jié),請閱讀: Sheng Liang and Gilad Bracha, Dynamic Class Loading in the Java Virtual Machine, ACM OOPSLA39。98, , 1998. 使用 。 。 比如。 import javassist.*。import 。public class Main { public static void main(String[] args) throws Throwable { ClassPool pool = ()。 Loader cl = new Loader(pool)。 CtClass ct = ()。 (())。 Class c = ()。 Object rect = ()。 : }}上面的代碼修改了類 。其父類被設置為 。當程序再次加載修改后的類時。 加一個監(jiān)聽事件滿足在加載時修改一個類。這個事件在類加載時被觸發(fā)。事件監(jiān)聽類需要實現(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)聽器通過 ()方法加入監(jiān)聽器的時候被調(diào)用。onLoad() 方法在class. onLoad()之前被調(diào)用,這樣就可以修改一個加載的類了。 比如,下面的監(jiān)聽器在類加載時將類訪問權(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 = (classname)。 ()。 }}注意 onLoad()方法不需要調(diào)用 toBytecode()或writeFile()方法。 要運行帶有MyTranslator的MyApp對象,代碼如下: import javassist.*。public class Main2 { public static void main(String[] args