1、首先,来学习用ASM创建一个类,以及将类倾倒成class文件。ASM在内存中创建一个类:

 

 
  
  1. ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS); 
  2.         // 类访问开始:必须 
  3.         cw.visit(Opcodes.V1_5, Opcodes.ACC_PUBLIC, "my/Foo"null
  4.                 "java/lang/Object"null); 
  5.  
  6.         // 构建构造函数 
  7.         MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>""()V"
  8.                 nullnull); 
  9.         // 代码开始:必须 
  10.         mv.visitCode(); 
  11.         mv.visitVarInsn(Opcodes.ALOAD, 0); 
  12.         mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object""<init>"
  13.                 "()V"); 
  14.         mv.visitInsn(Opcodes.RETURN); 
  15.  
  16.         // 计算栈和局部变量最大空间:必须,不计算会报java.lang.VerifyError异常,异常信息是:Stack size too large 
  17.         mv.visitMaxs(00); 
  18.         // 代码结束:必须 
  19.         mv.visitEnd(); 
  20.  
  21.         // 构建main函数 
  22.         mv = cw.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "main"
  23.                 "([Ljava/lang/String;)V"nullnull); 
  24.         mv.visitCode(); 
  25.         mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System""out"
  26.                 "Ljava/io/PrintStream;"); 
  27.         mv.visitLdcInsn("Hello World!"); 
  28.         mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream"
  29.                 "println""(Ljava/lang/String;)V"); 
  30.         mv.visitInsn(Opcodes.RETURN); 
  31.         mv.visitMaxs(00); 
  32.         mv.visitEnd(); 
  33.  
  34.         // 类结束:必须 
  35.         cw.visitEnd(); 

2、在内存中创建出该类之后,怎么将该类的字节码导出到数组?

继续上面的代码,go on…

 
 
  
  1. // 将在内存中创建的my.Foo类字节码导出到字节数组 
  2.     final byte[] bs = cw.toByteArray(); 
3、对于独立的类(不依赖自己创建的用户类,或者被依赖的类已经存在,且在运行时类路径上),有什么简单的办法可以验证创建的类字节码的合理性?
答:自定义类加载器,载入先前导出的类字节码的字节数组(参见问题2)。
 
 
  
  1. // 加载my.Foo类的字节码 
  2.         Class clazz = new ClassLoader() { 
  3.             protected Class findClass(String name) 
  4.                     throws ClassNotFoundException { 
  5.                 return defineClass(name, bs, 0, bs.length); 
  6.             } 
  7.         }.loadClass("my.Foo"); 

如果能够成功载入,虚拟机自然不会报错,如若不然,虚拟机会报错,譬如:

如果插装插两次,JVM虚拟机在载入该类的时候,一定会报Exception in thread "main" java.lang.ClassFormatError: Duplicate field name&signature in class file my/Foo 这样的错误。

4、类载入后,可以结合Java反射的能力,创建对象,访问字段,访问方法:

 

 
   
  1. // 利用反射获取my.Foo类中的main方法 
  2.     Method method = clazz.getMethod("main"new Class[] { String[].class }); 
  3.     // 用反射的方式调用my.Foo类中的main() 
  4.     method.invoke(null, (Object) new String[0]); 

5、很多时候,需要将字节码从内存中倾倒出来,写成class文件:

 
   
  1. OutputStream out = new FileOutputStream("d:/my/Foo.class"); 
  2.     out.write(bs); 
  3.     out.close(); 

6、接下来,继续深入下去,读取class文件,并且,修改它,然后再倾倒出一个新的class文件:

 

怎么读取类字节码?方法有很多:

a) 方法一:

Cla***eader cr = new Cla***eader("java.lang.Runnable");

b) 方法二:

InputStream is = cl.getResourceAsStream(classname.replace(’.’, ’/’) + ".class");

然后

Cla***eader cr = new Cla***eader(is);

c) 方法三:

URL url = HelloModifyASM.class.getResource("Foo.class");

Cla***eader cr = new Cla***eader(url.openStream());

适用场景各不同,有选择的用便是。

7、在类中添加一个静态字段,比如public static List _my_instances; 怎么做?

 

创建ClassAdapter的子类,然后覆写

 

 
   
  1. void visit(int version, int access, String name, 
  2.                     String signature, String superName, String[] interfaces) 
  3. 这个方法,并在方法里边调用 visitField语句: 
  4. // 添加字段:public static List _my_instances; 
  5. super.visitField(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, 
  6.                         "_my_instances""Ljava/util/List;"nullnull); 

ASM,确实强!不得不服!呵呵

8、如何添加类的静态初始化块?

继续在ClassAdapter的visit方法中写visitMethod方法,为啥这么用?因为visit方法只执行一次,在这里写也是合适的位置。

 

 
   
  1. // 添加静态的初始化块 
  2. MethodVisitor mv = super.visitMethod(Opcodes.ACC_STATIC, "<clinit>"
  3.                         "()V"nullnull); 
  4. mv.visitCode(); 
  5. mv.visitTypeInsn(Opcodes.NEW, "java/util/ArrayList"); 
  6. mv.visitInsn(Opcodes.DUP); 
  7. mv.visitMethodInsn(Opcodes.INVOKESPECIAL, 
  8.                         "java/util/ArrayList""<init>""()V"); 
  9. mv.visitFieldInsn(Opcodes.PUTSTATIC, "my/Foo""_my_instances"
  10.                         "Ljava/util/List;"); 
  11. mv.visitInsn(Opcodes.RETURN); 
  12. mv.visitMaxs(00); 
  13. mv.visitEnd(); 

9、我想要修改无参构造函数,想在其中添加几条语句,怎么做?

和上面一样,同样是在要ClassAdapter的子类中动手脚,不过,此时要覆写的方法是:

 
   
  1. MethodVisitor visitMethod(int access, String name, 
  2.                     String desc, String signature, String[] exceptions) 

我们当然是要通过name和desc找到我们要处理的函数,然后再修改它。name就是函数名,当然,有些名字是编译器创建的(不是你在编码的时候写的,比如<init>,再比如<clinit>),desc则是虚拟机内部为方法的类型生成的描述符号,比如()V,表示无参数且返回void的函数。

 
   
  1. public MethodVisitor visitMethod(int access, String name, 
  2.                     String desc, String signature, String[] exceptions) { 
  3.                 MethodVisitor mv = super.visitMethod(access, name, desc, 
  4.                         signature, exceptions); 
  5.                  
  6.                 // 修改无参的构造函数: 
  7.                 if (!"<init>".equals(name) || !"()V".equals(desc)) 
  8.                     return mv; 
  9.                 return new MethodAdapter(mv) { 
  10.                     public void visitInsn(int opcode) { 
  11.                         if (opcode == Opcodes.RETURN) { 
  12.                             visitFieldInsn(Opcodes.GETSTATIC, "my/Foo"
  13.                                     "_my_instances""Ljava/util/List;"); 
  14.                             visitVarInsn(Opcodes.ALOAD, 0); 
  15.                             visitMethodInsn(Opcodes.INVOKEINTERFACE, 
  16.                                     "java/util/List""add"
  17.                                     "(Ljava/lang/Object;)Z"); 
  18.                              
  19.                              
  20.                             visitInsn(Opcodes.POP); 
  21.                             visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System""out""Ljava/io/PrintStream;"); 
  22.                             visitTypeInsn(Opcodes.NEW, "java/lang/StringBuilder"); 
  23.                             visitInsn(Opcodes.DUP); 
  24.                             visitLdcInsn("size: "); 
  25.                             visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/StringBuilder""<init>""(Ljava/lang/String;)V"); 
  26.                             visitFieldInsn(Opcodes.GETSTATIC, "my/Foo""_my_instances""Ljava/util/List;"); 
  27.                             visitMethodInsn(Opcodes.INVOKEINTERFACE, "java/util/List""size""()I"); 
  28.                             visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder""append""(I)Ljava/lang/StringBuilder;"); 
  29.                             visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder""toString""()Ljava/lang/String;"); 
  30.                             visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream""println""(Ljava/lang/String;)V"); 
  31.                              
  32.                         } 
  33.                         super.visitInsn(opcode); 
  34.                     } 
  35.                 }; 
  36.             } 
  37.         }; 

我这里的操作是在无参构造函数返回之前,插入了一些指令。如果想要插入的代码比较复杂,指令肯定会很复杂,我是怎么知道要这么写的?这个问题嘛,有点难,不过呢,也是有招解的。当当当~~~当!看下面,哈哈。

10、ASM中提供了一个强悍的工具类:ASMifierClassVisitor,这个类很牛叉。给它一个类,它能把用ASM怎么生成出这个类的字节码的代码给出来。这个功能实在是太强悍了!!从细节上,我们也能看到ASM的良苦用心。赞一个!

 

asm guide上面是这么写的:

java -classpath asm.jar:asm-util.jar \

org.objectweb.asm.util.ASMifierClassVisitor \

java.lang.Runnable

其实吧,不适用于新版本。因为新版本的jar包名字改变了哈。最新版本,要看我的:

D:\tcc3\3.0\trunk\HelloASM\bin>java -cp ../lib/asm-3.3.1.jar;../lib/asm-util-3.3

.1.jar org.objectweb.asm.util.ASMifierClassVisitor com/taobao/Foo.class

请根据自己的实际情况,修改-cp部分的jar包的路径,我这里用的是相对路径,不要问我为什么用com/taobao/Foo.class,而不是com.taobao.Foo.class。因为,我也是试验出来的哈。如果写成com.taobao.Foo.class,会报错:

Exception in thread "main" java.io.FileNotFoundException: com.taobao.Foo.class (

系统找不到指定的文件。)

        at java.io.FileInputStream.open(Native Method)

        at java.io.FileInputStream.<init>(Unknown Source)

        at java.io.FileInputStream.<init>(Unknown Source)

        at org.objectweb.asm.util.ASMifierClassVisitor.main(Unknown Source)

当然啦。如果你仔细研读ASM的源码,估计也能解决。J

不过,我是凭直觉,一修改就对啦。哈哈

此文是在一年前写的,当时在搞ASM相关的一些研究,也从其他网友的博客进行了学习,里边也有自己探索的一些成分。现在,把它回馈出来,我为人人,人人为我,呵呵。