Label label = new Label()
这个语句中,label的作用是为了条件跳转,其实也可以理解成字节码指令的参数。
所以label必须对应一条字节码指令,通过visitLabel(label)来调用,并且visitLabel的调用必须紧跟随着label对象指定的指令。
如例子中,第一个label指向goto后,所以顺序必须是:mv.visitJumpInsn(Opcodes.GOTO, end);
当ASM的ClassReader读取到Method时就转入MethodVisitor接口处理。
方法的定义,以及方法中指令的定义都会通过MethodVisitor接口通知给程序。我们假设有下面这样的一个类:
下面是这个MethodVisitor接口的所有方法定义。本文只会介绍主要的方法,因此不会逐个对方法做依次介绍:
这些方法必须按照以下顺序调用(和MethodVisitor接口在Javadoc中指定的一些额外约束):
visitAnnotationDefault? ( visitAnnotation | visitParameterAnnotation | visitAttribute )\* ( visitCode ( visitTryCatchBlock | visitLabel | visitFrame | visitXxx Insn | visitLocalVariable | visitLineNumber ) \* visitMaxs )? visitEnd
这意味着,如有注释和属性的话,则必须先访问,后面是非抽象方法的字节码。
对于这些方法,这些代码必须按顺序访问,在唯一一个‘visitCode’方法调用和唯一一个‘visitMaxs’方法调用之间。
该接口的方法数量如此之多,甚至是ClassVisitor接口的3倍以上。但是值得关心的接口只有下面这几个,其余的都是和代码有关系:
visitCode
ASM开始扫描这个方法。
visitMaxs(maxStack, maxLocals);
该方法是visitEnd之前调用的方法,可以反复调用。
用以确定类方法在执行时候的堆栈大小。
visitEnd();
表示方法输出完毕
visitCode 和 visitMaxs 方法可用于检测该方法的字节代码在一个事件序列中的
开始与结束。和类的情况一样,visitEnd 方法也必须在最后调用,用于检测一个方法在一个事件序列中的结束。
visitMethodInsn
/** * 访问方法的指令。 方法指令是调用方法的指令。 * * @param opcode 要访问的类型指令的操作码。可以是INVOKEVIRTUAL,INVOKESPECIAL,INVOKESTATIC或INVOKEINTERFACE。 * @param owner 方法的所有者类的内部名称 (see {@link * Type#getInternalName()}). * @param name 方法名 * @param descriptor the method's descriptor (see {@link Type}). * @param isInterface if the method's owner class is an interface. */ public void visitMethodInsn( final int opcode, final String owner, final String name, final String descriptor, final boolean isInterface) { if (api < Opcodes.ASM5 && (opcode & Opcodes.SOURCE_DEPRECATED) == 0) { if (isInterface != (opcode == Opcodes.INVOKEINTERFACE)) { throw new UnsupportedOperationException("INVOKESPECIAL/STATIC on interfaces requires ASM5"); } visitMethodInsn(opcode, owner, name, descriptor); return; } if (mv != null) { mv.visitMethodInsn(opcode & ~Opcodes.SOURCE_MASK, owner, name, descriptor, isInterface); } }
visitVarInsn
访问局部变量指令。 局部变量指令是加载loads或存储stores局部变量值的指令。
/** * @param opcode 要访问的局部变量指令的操作码。 该操作码是 * ILOAD, LLOAD, FLOAD, DLOAD, ALOAD, ISTORE, LSTORE, FSTORE, DSTORE, ASTORE or RET. * @param var 要访问的指令的操作数。该操作数是局部变量的索引。 */ public void visitVarInsn(final int opcode, final int var) { if (mv != null) { mv.visitVarInsn(opcode, var); } }
需要注意的是,没有必要为了开始访问另外一个方法,而结束当前访问的方法。
实际上,‘MethodVisitor’实例间是完全独立的,可以用任何顺序调用(但必须在‘cv.visitEnd()’调用之前使用):
ASM提供了三个基于MethodVisitor API的核心组件,用于生成和转换方法:
ClassReader类解析一个编译后的方法,并且通过传递ClassVisitor作为accept方法的参数获得的返回,调用MethodVisitor’相应的方法。
ClassWriter的‘visitMethod’返回了MethodVisitor抽象类的一个实现,该实现可以直接用二进制的方式构建编译后的方法。
MethodVisitor类可以传递所有调用它的方法给另一个MethodVisitor类。MethodVisitor类可以看作一个事件过滤器。
实现类 - MethodWriter
生成相应的“ method_info”结构的MethodVisitor,如Java虚拟机规范(JVMS)中所定义。
visitMaxs
@Override public void visitMaxs(final int maxStack, final int maxLocals) { if (compute == COMPUTE_ALL_FRAMES) { computeAllFrames(); } else if (compute == COMPUTE_MAX_STACK_AND_LOCAL) { computeMaxStackAndLocal(); } else if (compute == COMPUTE_MAX_STACK_AND_LOCAL_FROM_FRAMES) { this.maxStack = maxRelativeStackSize; } else { this.maxStack = maxStack; this.maxLocals = maxLocals; } }
AnalyzerAdapter实现类
一个MethodVisitor,用于跟踪visitFrame调用之间的 stack map frame 更改。 该适配器必须与ClassReader.EXPAND_FRAMES选项一起使用。 每个visitX指令都将委托给链中的下一个访问者(如果有),然后模拟该指令对 stack map frame(由局部变量和堆栈表示)的影响。 链中的下一个访问者可以通过读取其visitX方法中的这些字段的值来获取每条指令之前的 stack map frame 的状态(这需要引用链中位于其之前的AnalyzerAdapter)。 如果此适配器与不包含堆栈映射表属性的类一起使用(即Java 6之前的类),则此适配器可能无法为每条指令计算堆栈映射框架。 在这种情况下,不会抛出异常,但是对于这些指令,locals和stack字段将为null。
这个方法适配器会根据 visitFrame 方法中被访问的帧,计算出每一个指令之前的栈哈希帧。
为了节省空间,visitFrame仅仅会在一个方法中某些特定的指令前调用,并且“其他的帧也可以从这些帧简单容易的推算出来”。这就是AnalyzerAdapter的作用。
当然在它仅能作用在包含了预先计算过栈哈希帧的编译类,即使用Java 6或者更改版本编译的类(或者像之前的示例一样,使用含有COMPUTE_FRAMES参数的ASM adapter将类升级到Java 6):
‘stack’属性在AnalyzerAdapter类中有定义,并且包含了在操作栈中的类型。
更确切地说,对于一个‘visitXxx Insn’指令,在覆盖方法被调用前,这个列表包含了在该条指令前操作栈的状态。
需要注意的是覆盖方法必须被调用,这样栈里的属性才能正确地更新(因此使用父类的原始方法,而不是mv的方法)。
另外,调用父类的方法也可以插入新指令:效果是AyalyzerAdapter会计算出这些指令对应的帧。
因此,该适配器会基于它计算出的帧更新visitMaxs方法的参数,我们就不必更新这些参数了: