一、什么是ASM
ASM是一个JAVA字节码分析、创建和修改的开源应用框架。在ASM中提供了诸多的API用于对类的内容进行字节码操作的方法。与传统的BCEL和SERL不同,在ASM中提供了更为优雅和灵活的操作字节码的方式。目前ASM已被广泛的开源应用架构所使用,例如:Spring、Hibernate等。
二、ASM能干什么
分析一个类、从字节码角度创建一个类、修改一个已经被编译过的类文件
三、ASM初探例子
这里我们使用ASM的CoreAPI(ASM提供了两组API:Core和Tree,Core是基于访问者模式来操作类的,而Tree是基于树节点来操作类的)创建一个Person类,目标类如下:
package test; public class Person { private String name; public Person(){ this.name = "Sum"; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
这个类在构造方法中初始化了属性name,并提供了两个public方法来修改和访问name属性。
接下来就要书写创建这个类的代码了,代码如下:
代码1:
package create; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.Opcodes; public class CreateClass implements Opcodes{ public static void createClass() throws IllegalArgumentException, SecurityException, IllegalAccessException, InvocationTargetException, NoSuchMethodException, InstantiationException{ ClassWriter cw = new ClassWriter(0); //Opcodes.V1_6指定类的版本 //Opcodes.ACC_PUBLIC表示这个类是public, //“test/Person”类的全限定名称 //第一个null位置变量定义的是泛型签名, //“java/lang/Object”这个类的父类 //第二个null位子的变量定义的是这个类实现的接口 cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, "test/Person", null, "java/lang/Object", null); ClassVisitor cv = new CreateClassAdapter(cw); cv.visitField(ACC_PRIVATE, "name", "Ljava/lang/String;", null, null); cv.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null).visitCode();; cv.visitMethod(ACC_PUBLIC, "getName", "()Ljava/lang/String;", null, null).visitCode();; cv.visitMethod(ACC_PUBLIC, "setName", "(Ljava/lang/String;)V", null, null).visitCode();; cv.visitEnd(); byte[] code = cw.toByteArray(); MyClassLoader classLoader = new MyClassLoader(); Class<?> exampleClass = classLoader.definClassFromClassFile("test.Person", code); for(Method method : exampleClass.getMethods()){ System.out.println(method); } System.out.println(exampleClass.getMethod("getName").invoke(exampleClass.newInstance(), null)); } public static void main(String args[]) throws IllegalArgumentException, SecurityException, IllegalAccessException, InvocationTargetException, NoSuchMethodException, InstantiationException{ createClass(); } } class MyClassLoader extends ClassLoader{ public Class definClassFromClassFile(String className, byte[] classFile) throws ClassFormatError { return defineClass(className, classFile, 0, classFile.length); } }
代码2:
package create; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; public class CreateClassAdapter extends ClassVisitor implements Opcodes{ public CreateClassAdapter(ClassVisitor cv) { super(Opcodes.ASM4, cv); } @Override public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { if (cv != null) { cv.visit(version, access, name, signature, superName, interfaces); } } @Override public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions); if(name.equals("<init>")){ return new CreateInitMethodAdapter(mv); }else if(name.equals("getName")){ return new CreateGetMethodAdapter(mv); }else if(name.equals("setName")){ return new CreateSetMethodAdapter(mv); }else{ return super.visitMethod(access, name, desc, signature, exceptions); } } }
代码3:
package create; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; public class CreateInitMethodAdapter extends MethodVisitor implements Opcodes{ public CreateInitMethodAdapter(MethodVisitor mv) { super(Opcodes.ASM4, mv); } @Override public void visitCode(){ mv.visitVarInsn(ALOAD, 0); mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V"); mv.visitVarInsn(ALOAD, 0); mv.visitLdcInsn("zhangzhuo"); mv.visitFieldInsn(PUTFIELD, "test/Person", "name", "Ljava/lang/String;"); mv.visitInsn(RETURN); mv.visitMaxs(2, 1); mv.visitEnd(); } }
代码4:
package create; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; public class CreateGetMethodAdapter extends MethodVisitor implements Opcodes{ public CreateGetMethodAdapter(MethodVisitor mv) { super(Opcodes.ASM4, mv); } @Override public void visitCode(){ mv.visitVarInsn(ALOAD, 0); mv.visitFieldInsn(GETFIELD, "test/Person", "name", "Ljava/lang/String;"); mv.visitInsn(ARETURN); mv.visitMaxs(1, 1); mv.visitEnd(); } }
代码5:
package create; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; public class CreateSetMethodAdapter extends MethodVisitor implements Opcodes{ public CreateSetMethodAdapter(MethodVisitor mv) { super(Opcodes.ASM4, mv); } @Override public void visitCode(){ mv.visitVarInsn(ALOAD, 0); mv.visitVarInsn(ALOAD, 1); mv.visitFieldInsn(PUTFIELD, "test/Person", "name", "Ljava/lang/String;"); mv.visitInsn(RETURN); mv.visitMaxs(2, 2); mv.visitEnd(); } }
运行结果:
public java.lang.String test.Person.getName() public void test.Person.setName(java.lang.String) public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException public final void java.lang.Object.wait() throws java.lang.InterruptedException public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException public boolean java.lang.Object.equals(java.lang.Object) public java.lang.String java.lang.Object.toString() public native int java.lang.Object.hashCode() public final native java.lang.Class java.lang.Object.getClass() public final native void java.lang.Object.notify() public final native void java.lang.Object.notifyAll() zhangzhuo
在写ASM的字节码指令,对很多人来说都很头疼,少不留神就容易写错,而且排查起来也很费劲。这里有个很实用的小窍门。想要通过ASM动态创建一个类,可以先在eclipse中新建该类,并且将类中所有方法都写好。通过bytecode插件查看这个类的ASM字节码指令,直接将其中的指令考过来就可以用。
就拿上面的例子:Person.java
通过ASM插件我们看到整个类的字节码指令是这样的
package asm.test; import java.util.*; import org.objectweb.asm.*; public class PersonDump implements Opcodes { public static byte[] dump () throws Exception { ClassWriter cw = new ClassWriter(0); FieldVisitor fv; MethodVisitor mv; AnnotationVisitor av0; cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, "test/Person", null, "java/lang/Object", null); cw.visitSource("Person.java", null); { fv = cw.visitField(ACC_PRIVATE, "name", "Ljava/lang/String;", null, null); fv.visitEnd(); } { mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null); mv.visitCode(); Label l0 = new Label(); mv.visitLabel(l0); mv.visitLineNumber(6, l0); mv.visitVarInsn(ALOAD, 0); mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false); Label l1 = new Label(); mv.visitLabel(l1); mv.visitLineNumber(7, l1); mv.visitVarInsn(ALOAD, 0); mv.visitLdcInsn("Sum"); mv.visitFieldInsn(PUTFIELD, "test/Person", "name", "Ljava/lang/String;"); Label l2 = new Label(); mv.visitLabel(l2); mv.visitLineNumber(8, l2); mv.visitInsn(RETURN); Label l3 = new Label(); mv.visitLabel(l3); mv.visitLocalVariable("this", "Ltest/Person;", null, l0, l3, 0); mv.visitMaxs(2, 1); mv.visitEnd(); } { mv = cw.visitMethod(ACC_PUBLIC, "getName", "()Ljava/lang/String;", null, null); mv.visitCode(); Label l0 = new Label(); mv.visitLabel(l0); mv.visitLineNumber(11, l0); mv.visitVarInsn(ALOAD, 0); mv.visitFieldInsn(GETFIELD, "test/Person", "name", "Ljava/lang/String;"); mv.visitInsn(ARETURN); Label l1 = new Label(); mv.visitLabel(l1); mv.visitLocalVariable("this", "Ltest/Person;", null, l0, l1, 0); mv.visitMaxs(1, 1); mv.visitEnd(); } { mv = cw.visitMethod(ACC_PUBLIC, "setName", "(Ljava/lang/String;)V", null, null); mv.visitCode(); Label l0 = new Label(); mv.visitLabel(l0); mv.visitLineNumber(15, l0); mv.visitVarInsn(ALOAD, 0); mv.visitVarInsn(ALOAD, 1); mv.visitFieldInsn(PUTFIELD, "test/Person", "name", "Ljava/lang/String;"); Label l1 = new Label(); mv.visitLabel(l1); mv.visitLineNumber(16, l1); mv.visitInsn(RETURN); Label l2 = new Label(); mv.visitLabel(l2); mv.visitLocalVariable("this", "Ltest/Person;", null, l0, l2, 0); mv.visitLocalVariable("name", "Ljava/lang/String;", null, l0, l2, 1); mv.visitMaxs(2, 2); mv.visitEnd(); } cw.visitEnd(); return cw.toByteArray(); } }
我们看到,Person的构造函数的指令:
{ mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null); mv.visitCode(); Label l0 = new Label(); mv.visitLabel(l0); mv.visitLineNumber(6, l0); mv.visitVarInsn(ALOAD, 0); mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false); Label l1 = new Label(); mv.visitLabel(l1); mv.visitLineNumber(7, l1); mv.visitVarInsn(ALOAD, 0); mv.visitLdcInsn("Sum"); mv.visitFieldInsn(PUTFIELD, "test/Person", "name", "Ljava/lang/String;"); Label l2 = new Label(); mv.visitLabel(l2); mv.visitLineNumber(8, l2); mv.visitInsn(RETURN); Label l3 = new Label(); mv.visitLabel(l3); mv.visitLocalVariable("this", "Ltest/Person;", null, l0, l3, 0); mv.visitMaxs(2, 1); mv.visitEnd(); }
这段就可以直接考到CreateInitMethodAdapter中,当然要去掉
mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null); mv.visitCode();
然后依次类推,Set方法,考到CreateSetMethodAdapter
{ mv = cw.visitMethod(ACC_PUBLIC, "setName", "(Ljava/lang/String;)V", null, null); mv.visitCode(); Label l0 = new Label(); mv.visitLabel(l0); mv.visitLineNumber(15, l0); mv.visitVarInsn(ALOAD, 0); mv.visitVarInsn(ALOAD, 1); mv.visitFieldInsn(PUTFIELD, "test/Person", "name", "Ljava/lang/String;"); Label l1 = new Label(); mv.visitLabel(l1); mv.visitLineNumber(16, l1); mv.visitInsn(RETURN); Label l2 = new Label(); mv.visitLabel(l2); mv.visitLocalVariable("this", "Ltest/Person;", null, l0, l2, 0); mv.visitLocalVariable("name", "Ljava/lang/String;", null, l0, l2, 1); mv.visitMaxs(2, 2); mv.visitEnd(); }
get方法考到CreateGetMethodAdapter
{ mv = cw.visitMethod(ACC_PUBLIC, "getName", "()Ljava/lang/String;", null, null); mv.visitCode(); Label l0 = new Label(); mv.visitLabel(l0); mv.visitLineNumber(11, l0); mv.visitVarInsn(ALOAD, 0); mv.visitFieldInsn(GETFIELD, "test/Person", "name", "Ljava/lang/String;"); mv.visitInsn(ARETURN); Label l1 = new Label(); mv.visitLabel(l1); mv.visitLocalVariable("this", "Ltest/Person;", null, l0, l1, 0); mv.visitMaxs(1, 1); mv.visitEnd(); }
这样是不是很简单