关于java字节码的处理,目前有很多工具,如bcel,asm。不过这些都需要直接跟虚拟机指令打交道。如果你不想了解虚拟机指令,可以采用javassist。javassist是jboss的一个子项目,其主要的优点,在于简单,而且快速。直接使用java编码的形式,而不需要了解虚拟机指令,就能动态改变类的结构,或者动态生成类。
    下面通过一个简单的例子,通过javassist来实现如何动态注入代码。
    假设,存在类A,如下:

public class A {
     public void method() {
         for (int i = 0; i < 1000000; i++) {
         }
         System.out.println("method1");
     }
 }


测试类B如下:

public class B {
     public static void main(String[] args) {
         A a = new A();
         a.method();    
     }
 }


现在想统计一下method的执行时间,
默认的实现是修改method:

public void method() {
         long start = System.currentTimeMillis();
         for (int i = 0; i < 1000000; i++) {
         }
         System.out.println("method1");
         long end = System.currentTimeMillis();
         System.out.println(end - start);
     }


如果A的方法很多,统计方法的执行时间的代码就会相应的增加。为了减少工作量,通过动态注入代码的形式来实现。
修改B的main方法:

public static void main(String[] args) throws Exception {
       //用于取得字节码类,必须在当前的classpath中,使用全称
         CtClass ctClass = ClassPool.getDefault().get("org.esoft.A");
          //需要修改的方法名称
         String mname = "method";        
         CtMethod mold = ctClass.getDeclaredMethod(mname);
          //修改原有的方法名称
         String nname = mname + "$impl";
         mold.setName(nname);
          //创建新的方法,复制原来的方法
         CtMethod mnew = CtNewMethod.copy(mold, mname, ctClass, null);
          //主要的注入代码
         StringBuffer body = new StringBuffer();
         body.append("{\nlong start = System.currentTimeMillis();\n");
         //调用原有代码,类似于method();($$)表示所有的参数
         body.append(nname + "($$);\n");
         body.append("System.out.println(\"Call to method "
                     + mname
                     + " took \" +\n (System.currentTimeMillis()-start) + "
                     + "\" ms.\");\n");
        
         body.append("}");
          //替换新方法
         mnew.setBody(body.toString());
          //增加新方法
         ctClass.addMethod(mnew);
         //类已经更改,注意不能使用A a=new A();,因为在同一个classloader中,不允许装载同一个类两次
         A a=(A)ctClass.toClass().newInstance();
         a.method();
     }


这只是简单的一个应用。javassist还提供了很多的功能,用于更改类结构。有兴趣的可以参考相关文档

使用javassist对.class文件进行修改


最近重新再看<Inside JVM>,对JAVA编译成的字节码结构很感兴趣,希望找个工具能够对.class文件进行的解析和查看。没找到,倒发现javaassist可以对字节码进行操作和修改。此工具是JBOSS项目的一部分,JBOSS实现AOP的基础。呵呵,开眼界了,原来我们可以直接对字节码文件进行修改,哪怕不知道源文件(跟反编译完全不同)。一个简单例子:

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(/"/");}");
         m.insertBefore("System.out.println(/"/");");
         Class c = cc.toClass();
         Hello h = (Hello)c.newInstance();
         h.say();
     }
 }

编译运行此文件,输出:

我们在

CtMethod m = cc.getDeclaredMethod("say");
   m.setBody("{System.out.println(/"/");}");  m.insertBefore("System.out.println(/"/");");
修改了say()方法,改成了
System.out.println("");
System.out.println("");

这里的ClassPool是CtClass的容器,它读取class文件,并根据要求保存CtClass的结构以便日后使用,默认状态下是从当前的类装载器获得,当然你可以指定:

pool.insertClassPath("/usr/local/javalib");

当然,不仅仅是修改方法,你还可以新建一个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.");
  }


    }
}
第一次运行时,因为Test里并没有g()方法,所以执行

CtMethod fMethod = cc.getDeclaredMethod("f");
      CtMethod gMethod = CtNewMethod.copy(fMethod, "g", cc, null);  //把f方法复制给g
      cc.addMethod(gMethod);
      cc.writeFile(); //更新class文件     System.out.println("g() was added.");
 打印:g() was added

第2次运行时,因为以上步骤已经在class文件中增加了一个g方法,所以

System.out.println("g() is already defined in sample.Test.");
 打印:g() is already defined in sample.Test

Javassist不仅能修改你自己的class文件,而且可以同样修改JDK自带的类库(废话,类库也是人写的^_^)具体请看它的tutorial。