最近重新再看<Inside JVM>,對JAVA編譯成的字節(jié)碼結(jié)構(gòu)很感興趣,希望找個工具能夠?qū)?class文件進(jìn)行的解析和查看。沒找到,倒發(fā)現(xiàn)javaassist可以對字節(jié)碼進(jìn)行操作和修改。此工具是JBOSS項(xiàng)目的一部分,JBOSS實(shí)現(xiàn)AOP的基礎(chǔ)。呵呵,開眼界了,原來我們可以直接對字節(jié)碼文件進(jìn)行修改,哪怕不知道源文件(跟反編譯完全不同)。一個簡單例子:
import javassist.*;
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.setBody("{System.out.println(/"shit/");}");
??????? m.insertBefore("System.out.println(/"fuck/");");
??????? Class c = cc.toClass();
??????? Hello h = (Hello)c.newInstance();
??????? h.say();
??? }
}
編譯運(yùn)行此文件,輸出:
fuck
shit
我們在
?CtMethod m = cc.getDeclaredMethod("say");
? m.setBody("{System.out.println(/"shit/");}");
? m.insertBefore("System.out.println(/"fuck/");");
修改了say()方法,改成了
System.out.println("fuck");
System.out.println("shit");
這里的ClassPool是CtClass的容器,它讀取class文件,并根據(jù)要求保存CtClass的結(jié)構(gòu)以便日后使用,默認(rèn)狀態(tài)下是從當(dāng)前的類裝載器獲得,當(dāng)然你可以指定:
pool.insertClassPath("/usr/local/javalib");
當(dāng)然,不僅僅是修改方法,你還可以新建一個class,利用makeClass()方法,如:
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.makeClass("Point");
還可以新增方法,下面是sample里的一個例子,同樣的:
package sample;
import javassist.*;
import java.lang.reflect.*;
/*
?? A very simple sample program
?? This program overwrites sample/Test.class (the class file of this
?? class itself) for adding a method g().? If the method g() is not
?? defined in class Test, then this program adds a copy of
?? f() to the class Test with name g().? Otherwise, this program does
?? not modify sample/Test.class at all.
?? To see the modified class definition, execute:
?? % javap sample.Test
?? after running this program.
*/
public class Test {
??? public int f(int i) {
??? ?i++;
?? ?return i;
??? }
??? public static void main(String[] args) throws Exception {
?ClassPool pool = ClassPool.getDefault();
?CtClass cc = pool.get("sample.Test");
?Test test=new Test();
?Class c=test.getClass();
?Method []method=c.getDeclaredMethods();
?for(int i=0;i<method.length;i++){
??System.out.println(method[i]);
?}
?try {
???? cc.getDeclaredMethod("g");
???? System.out.println("g() is already defined in sample.Test.");
?}
?catch (NotFoundException e) {
???? /* getDeclaredMethod() throws an exception if g()
????? * is not defined in sample.Test.
????? */
???? CtMethod fMethod = cc.getDeclaredMethod("f");
???? CtMethod gMethod = CtNewMethod.copy(fMethod, "g", cc, null);
???? cc.addMethod(gMethod);
???? cc.writeFile();?// update the class file
???? System.out.println("g() was added.");
?}
??? }
}
第一次運(yùn)行時(shí),因?yàn)門est里并沒有g(shù)()方法,所以執(zhí)行
?CtMethod fMethod = cc.getDeclaredMethod("f");
???? CtMethod gMethod = CtNewMethod.copy(fMethod, "g", cc, null);? //把f方法復(fù)制給g
???? cc.addMethod(gMethod);
???? cc.writeFile();?//更新class文件
???? System.out.println("g() was added.");
打印:g() was added
第2次運(yùn)行時(shí),因?yàn)橐陨喜襟E已經(jīng)在class文件中增加了一個g方法,所以
?System.out.println("g() is already defined in sample.Test.");
打印:g() is already defined in sample.Test
?
Javassist不僅能修改你自己的class文件,而且可以同樣修改JDK自帶的類庫(廢話,類庫也是人寫的^_^)具體請看它的tutorial。