ASM

它是通过操作字节码来实现的,效率最高,但也最复杂。它有两种处理方法一种是基于(生产-ClassReader、过滤器-ClassVisitor、消费-ClassWrite)体系设计的事件驱动的,另一种是基于TreeAPI的文档解析的;

<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
<version>8.0.1</version>
</dependency>

<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm-commons</artifactId>
<version>8.0.1</version>
</dependency>

使用ASM时要使用单独的包引用,它的核心包有:core api、tree api、commons、util、xml。现在还有更多,而不要引用asm-all,因为这个包版本太低。在使用ASM需要注意调用时序,ASM是直接修改.class文件,所以需要了解调用顺序,以及文本修改的地方,比如整个代码块加trycatch,其start label应该放在visitCode之后,end放在visitMaxs之前;

accept-->visit-->visitSource-->visitOuterClass-->visitAnnotation*-->visitInnerClass-->visitField*-->visitMethod*-->end,它所有的读和写操作都是通过复写Visitor抽象类中的方法达到的。

public class MyMain {


public int a = 0;

public int b = 1;


public void test01(){


}


public void test02(){


}

}

public void asmField() throws IOException {

ClassReader cr = new ClassReader(bytes);

/*参数指定是否计算stack,建议1.6前用ClassWriter.COMPUTE_MAXS:只计算最大堆栈大小和局部变量表大小

1.6后用ClassWriter.COMPUTE_FRAMES,还会计算stackMapTable大小*/

ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);

ClassVisitor cv = new ClassVisitor(ASM5, cw) {



@Override

public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {

return super.visitField(access, name, desc, signature, value);

}


@Override

public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {

return super.visitMethod(access, name, desc, signature, exceptions);

}


@Override

public void visitEnd() {

super.visitEnd();

}


};


cr.accept(cv, ClassReader.SKIP_CODE);

bytes = cw.toByteArray();

FileUtils.writeByteArrayToFile(new File(classPath), bytes);


}

读取方法和字段

@Override

public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {

return super.visitField(access, name, desc, signature, value);


}

新增字段和方法

public void visitEnd() {

super.visitEnd();

FieldVisitor fv = cv.visitField(Opcodes.ACC_PUBLIC, "xyz", "Ljava/lang/String", null,null);

fv.visitEnd();

}

bytes = cw.toByteArray();

FileUtils.writeByteArrayToFile(new File(classPath), bytes);//在最后需要再写回去

删除字段和方法

这是通过返回null实现的,下例是删除test01方法

public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {

if ("test01".equals(name)){

return null;

}

return super.visitMethod(access, name, desc, signature, exceptions);

}

修改方法内容

其实现思路是先删除再添加

@Override

public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {

if ("test01".equals(name)){

return null;

}

return super.visitMethod(access, name, desc, signature, exceptions);

}


@Override

public void visitEnd() {

super.visitEnd();

MethodVisitor mv = cv.visitMethod(Opcodes.ACC_PUBLIC, "test01", "(ILjava/lang/String;)V", null,null);

mv.visitCode();

Label label0 = new Label();

mv.visitLabel(label0);//这个标签可以给字节码指令地址打标,标记特定的字节码位置,用于手续跳转等;

mv.visitLineNumber(17, label0);

mv.visitIntInsn(Opcodes.BIPUSH, 100);

mv.visitInsn(Opcodes.IRETURN);

//这行代码需要和ClassWriter类初始化配合使用,详细参考其注释

mv.visitMaxs(2, 1);

mv.visitEnd();

}

AdviceAdapter

它有两个核心方法:1、onMethodEnter,方法执行前;2、onMethodExie,方法正常或异常退出时调用。

@Override

public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {

//这行代码有问题,有了这么就不能修改了

MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);

return new AdviceAdapter(ASM5, mv, access, name, desc) {

@Override

protected void onMethodEnter() {

super.onMethodEnter();


mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");

mv.visitLdcInsn("onMethodEnter");

mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);

}


@Override

protected void onMethodExit(int opcode) {

super.onMethodExit(opcode);

if(opcode == Opcodes.ATHROW){

mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");

mv.visitLdcInsn("exception");

mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);

}

}

};

ASM生成HelloWorld

public class AsmHelloWorld extends ClassLoader implements Opcodes {

public static void main(final String args[]) throws Exception {

ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);

cw.visit(V1_7, ACC_PUBLIC, "Example", null, "java/lang/Object", null);

MethodVisitor mw = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);

mw.visitVarInsn(ALOAD, 0);

mw.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V");

mw.visitInsn(RETURN);

mw.visitMaxs(0, 0);

mw.visitEnd();

mw = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);

mw.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");

mw.visitLdcInsn("Hello World!");

mw.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V");

mw.visitInsn(RETURN);

mw.visitMaxs(0, 0);

mw.visitEnd();

byte[] code = cw.toByteArray();


AsmHelloWorld loader = new AsmHelloWorld();

Class exampleClass = loader.defineClass("Example", code, 0, code.length);

exampleClass.getMethods()[0].invoke(null, new Object[] { null });

}

}