本文是《》的后续博文。在上一篇文章中介绍了如何使用 ASM 动态安插代码到类中,从而简单实现 Aop。文章得到了广大朋友好评,我也希望可以不负众望继续写出可以得到大家认可的更多相关文章。本文主要讲解 ASM 核心接口方法和其参数意义。另外本文也可用做参考手册使用。
在上一篇文章中着重介绍了 ClassVisitor 接口,在本文将重点介绍一下MethodVisitor接口。前文提到过Class的文件结构类似
Class Annotation Annotation ... Field Annotation ... Method Annotation ...
当ASM的ClassReader读取到Method时就转入MethodVisitor接口处理。方法的定义,以及方法中指令的定义都会通过MethodVisitor接口通知给程序。我们假设有下面这样的一个类:
public class DemoClass { public static void main(String[] args) { System.out.println(); }}
通过Javap可以得到下面这样的输出:
$ javap -c classtest.DemoClassCompiled from "DemoClass.java"public class classtest.DemoClass extends java.lang.Object{public classtest.DemoClass(); Code: 0: aload_0 1: invokespecial #8; //Method java/lang/Object."":()V 4: returnpublic static void main(java.lang.String[]); Code: 0: getstatic #16; //Field java/lang/System.out:Ljava/io/PrintStream; 3: invokevirtual #22; //Method java/io/PrintStream.println:()V 6: return}
可以看出其实Java编译完这个类之后是产生了两个方法。其中一个是第四行表示的“public classtest.DemoClass();”它是构造方法。和第十行表示的“main”方法。下面这段例子用来扫描这个类的这两个方法,我们的扫描逻辑很简单就是当遇到一个定义的方法时输出这个方法名。
public class DemoClassTest { public static void main(String[] args) throws IOException { ClassReader cr = new ClassReader(DemoClass.class.getName()); cr.accept(new DemoClassVisitor(), ClassReader.SKIP_DEBUG); System.out.println("---ALL END---"); }}class DemoClassVisitor extends ClassVisitor { public DemoClassVisitor() { super(Opcodes.ASM4); } public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { System.out.println("at Method " + name); // MethodVisitor superMV = super.visitMethod(access, name, desc, signature, exceptions); return new DemoMethodVisitor(superMV, name); }}class DemoMethodVisitor extends MethodVisitor { private String methodName; public DemoMethodVisitor(MethodVisitor mv, String methodName) { super(Opcodes.ASM4, mv); this.methodName = methodName; } public void visitCode() { System.out.println("at Method ‘" + methodName + "’ Begin..."); super.visitCode(); } public void visitEnd() { System.out.println("at Method ‘" + methodName + "’End."); super.visitEnd(); }}
- 上面这段程序我们首先在第三行使用 ClassReader 去读取 DemoClass 类的字节码信息。
- 其次通过“cr.accept(new DemoClassVisitor(), ClassReader.SKIP_DEBUG);”方法开始Visitor扫描整个字节码。
- SKIP_DEBUG选项的意义是在扫描过程中掠过所有有关行号方面的内容。
- 在DemoClassVisitor类中我们重写了visitMethod方法,当遇到方法的时候打印出方法名。
- 随后我们返回DemoMethodVisitor对象,用以输出方法的开始和结束。
上面这段程序的输出如下:
at Methodat Method ‘ ’ Begin...at Method ‘ ’End.at Method mainat Method ‘main’ Begin...at Method ‘main’End.---ALL END---
下面是这个MethodVisitor接口的所有方法定义。本文只会介绍主要的方法,因此不会逐个对方法做依次介绍:
虽然该接口的方法数量如此之多,甚至是ClassVisitor接口的3倍以上。但是值得我们关心的接口只有下面这几个,其余的都是和代码有关系:
MethodVisitor.visitCode();MethodVisitor.visitMaxs(maxStack, maxLocals);MethodVisitor.visitEnd();
- 第一个方法:表示ASM开始扫描这个方法。
- 第二个方法:该方法是visitEnd之前调用的方法,可以反复调用。用以确定类方法在执行时候的堆栈大小。
- 第三个方法:表示方法输出完毕。
构造方法:
关于方法名或许读者注意到了在扫描这个类的时候,有一个特殊的方法被扫描到了“<init>”,这个方法是传说中的构造方法。当Java在编译的时候没有发现类文件中有构造方法的定义会为其创建一个默认的无参构造方法。这个“<init>”就是那个由系统添加的构造方法。现在我们为类填写一个构造方法如下:
public class DemoClass { public DemoClass(int a) { } public static void main(String[] args) { System.out.println(); }}
再次扫描这个类,你会发现它的结果和刚才是一样的,这是由于我们编写的构造方法替换了系统默认生成的那个。
静态代码块:
在Class我们接触过用“static { }”包含的代码,这个是我们常说的静态代码块。这个代码快ASM在扫描字节码的时候也会遇到它,大家可千万别以为这真的是一个什么代码块。所有的静态代码快最后都会放到“<clinit>”方法中。
静态代码快只有一个,现有下面这个的一个类。在编写这个类的时候我有意的写了两个不同的静态代码块的类:
public class DemoClass { static { int a; System.out.println(11); } public static void main(String[] args) { System.out.println(); } static { int a; System.out.println(22); }}
ASM在扫描这个类的时候你会发现虽然类中存在多个静态代码快,但是最后类文件中只会出现了一个“<clinit>”方法。JVM在编译Class的时候估计已经将多个静态代码块合并到一起了。