本文是《深入字节码 -- 使用 ASM 实现 AOP》的后续博文。在上一篇文章中介绍了如何使用 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.DemoClass Compiled 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."<init>":()V 4: return public 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(); } }
上面这段程序的输出如下:
at Method <init> at Method ‘<init>’ Begin... at Method ‘<init>’End. at Method main at Method ‘main’ Begin... at Method ‘main’End. ---ALL END---
下面是这个MethodVisitor接口的所有方法定义。本文只会介绍主要的方法,因此不会逐个对方法做依次介绍:
虽然该接口的方法数量如此之多,甚至是ClassVisitor接口的3倍以上。但是值得我们关心的接口只有下面这几个,其余的都是和代码有关系:
MethodVisitor.visitCode(); MethodVisitor.visitMaxs(maxStack, maxLocals); MethodVisitor.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的时候估计已经将多个静态代码块合并到一起了。