Javassist 讀寫位元組碼
原文地址:奇舞移動技術
Javassist是一個用於處理Java位元組碼的庫。Java位元組碼以二進位制的形式儲存在class檔案中。 每個class檔案包含一個Java類或介面。
class檔案可以用Javassist.CtClass類來表示。CtClass物件用於處理class檔案。以下是一個簡單的例子,有兩個類,這兩個類沒有關係,我們修改Rectangle使他的父類程式設計Point。
ClassPool pool = ClassPool.getDefault(); CtClass cc = pool.get("com.democome.Rectangle"); cc.setSuperclass(pool.get("com.democome.Point")); cc.writeFile(); 複製程式碼
Rectangle.java
public class Rectangle { private int width; private int height; } 複製程式碼
Point.java
public class Point { } 複製程式碼
執行之後,用反編譯工具可以看一下,繼承關係已經改變:

上面的程式碼首先獲得一個ClassPool。ClassPool是CtClass的容器。它讀取class檔案來構造CtClass物件,並記錄。要修改calss,首先要從ClassPool通過get()獲取一個CtClass物件。
關於實現的原理ClassPool中有一個Hashtable來儲存CtClass,key就是類名。ClassPool的get()方法如果能找到這個類則直接返回,否則建立一個CtClass物件返回,並存到Hashtable中。
private Hashtable cflow = null;
CtClass物件對class檔案進行修改,並呼叫writeFile()寫入檔案。Javassist還提供了一種直接獲取修改後的位元組碼的方法:
byte[] b = cc.toBytecode(); 複製程式碼
也可以直接載入class:
Class clazz = cc.toClass(); 複製程式碼
定義一個類
定義一個類,需要呼叫ClassPool的makeClass()方法。
ClassPool pool = ClassPool.getDefault(); CtClass cc = pool.makeClass("Point"); 複製程式碼
以上程式碼定義了一個類Point。可以用CtNewMethod建立一個方法,並使用CtClass中的addMethod()方法新增到Point。
cc.addMethod(CtNewMethod.make("public void hello(){System.out.print(\"hello\");}", cc)); cc.writeFile(); 複製程式碼

如果要建立新介面,可以用ClassPool中的makeInterface()方法。可以用CtNewMethod中的abstractMethod()建立介面中的成員方法。
ClassPool pool = ClassPool.getDefault(); CtClass cc = pool.makeInterface("Point"); cc.addMethod(CtNewMethod.abstractMethod(CtClass.voidType, "hello", null, null, cc)); cc.writeFile(); 複製程式碼

凍結類
如果通過writeFile(),toClass()或toBytecode()將CtClass物件轉換為類檔案,Javassist將凍結該CtClass物件。 不允許對該CtClass物件進行進一步修改。 這是為了在開發人員嘗試修改已載入的類檔案時警告開發人員,因為JVM不允許重新載入類。
凍結的CtClass可以解凍,以便允許修改類定義。 例如,

解凍之後可以修改,如下:
ClassPool pool = ClassPool.getDefault(); CtClass cc = pool.get("com.democome.Rectangle"); cc.writeFile(); cc.defrost(); cc.setSuperclass(pool.get("com.democome.Point")); 複製程式碼
如果ClassPool.doPruning設定為true,那麼當Javassist凍結該物件時,Javassist會修剪CtClass物件中包含的資料結構。修剪過的CtClass物件無法再次解凍。ClassPool.doPruning的預設值為false。要禁止修剪特定的CtClass,必須事先在該物件上呼叫stopPruning():
類搜尋路徑
ClassPool.getDefault()返回的ClassPool,類搜尋路徑和JVM相同。如果程式在諸如JBoss和Tomcat之類的Web應用程式伺服器上執行,那麼ClassPool可能無法找到使用者類,因為這樣的Web應用程式伺服器使用多個類載入器以及系統類載入器。在這種情況下,必須在ClassPool中註冊其他類路徑。
pool.insertClassPath(new ClassClassPath(this.getClass())); 複製程式碼
以上程式碼註冊用於載入此引用的物件的類的類路徑。可以使用任何Class物件作為引數而不是this.getClass()。用於載入由該Class表示的類的類路徑。
還可以將目錄註冊為類搜尋路徑。 例如,以下程式碼將目錄/usr/local/javalib新增到搜尋路徑:
ClassPool pool = ClassPool.getDefault(); pool.insertClassPath("/usr/local/javalib"); 複製程式碼
搜尋路徑還可以是URL:
ClassPool pool = ClassPool.getDefault(); ClassPath cp = new URLClassPath("www.javassist.org", 80, "/java/", "org.javassist."); pool.insertClassPath(cp); 複製程式碼
以上程式碼將“ www.javassist.org:80/java/”新增到類搜…
http://www.javassist.org:80/java/org/javassist/test/Main.class 複製程式碼
還可以使用ByteArrayClassPath直接向ClassPool物件提供一個位元組陣列,並從該陣列構造一個CtClass物件。例如:
ClassPool cp = ClassPool.getDefault(); CtClass cc = cp.get("com.democome.Rectangle"); byte[] b = cc.toBytecode(); String name = "Rectangle"; cp.insertClassPath(new ByteArrayClassPath(name, b)); CtClass cc2 = cp.get(name); 複製程式碼
如果不知道該類的完全限定名,則可以在ClassPool中使用makeClass():
ClassPool cp = ClassPool.getDefault(); InputStream ins = an input stream for reading a class file; CtClass cc = cp.makeClass(ins); 複製程式碼
例如:
ClassPool cp = ClassPool.getDefault(); InputStream ins = new FileInputStream( "/Users/yangpeng/Documents/workspace/javassist/Javassist/com/democome/Rectangle.class"); CtClass cc = cp.makeClass(ins); System.out.println(cc.getName()); 複製程式碼
列印結果如下:
com.democome.Rectangle 複製程式碼