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 }); } } |