ASM(四) 利用Method 组件动态注入方法逻辑

这篇继续结合例子来深入了解下Method组件动态变更方法字节码的实现。通过前面一篇,知道ClassVisitor 的visitMethod()方法可以返回一个MethodVisitor的实例。那么我们也基本可以知道,同ClassVisitor改变类成员一样,MethodVIsistor如果需要改变方法成员,注入逻辑,也可以通过继承MethodVisitor,来编写一个MethodXXXAdapter来实现对于方法逻辑的注入。通过下面的两个例子来介绍下无状态注入和有状态注入方法逻辑的实现。例子主要参考官方文档介绍,大家按照这个思路可以扩展更多种场景的应用。

一、无状态注入

先看一个例子,也是比较常见的一种场景,我们需要给下面这个类的所有方法注入一个计时的逻辑。

源码如下:

package asm.core.methord;/** * Created by yunshen.ljy on 2015/6/29. */public class Time {public void myCount() throws Exception {int i = 5;int j = 10;System.out.println(j – i);}public void myDeal() {try {int[] myInt = { 1, 2, 3, 4, 5 };int f = myInt[10];System.out.println(f);} catch (ArrayIndexOutOfBoundsException e) {e.printStackTrace();}}} 我们目标的class 字节码如下:

//// Source code recreated from a .class file by IntelliJ IDEA// (powered by Fernflower decompiler)//package asm.core.methord;public class Time {public static long timer;public Time() {}public void myCount() throws Exception {timer -= System.currentTimeMillis();byte i = 5;byte j = 10;System.out.println(j – i);timer += System.currentTimeMillis();}public void myDeal() {timer -= System.currentTimeMillis();try {int[] e = new int[]{1, 2, 3, 4, 5};int f = e[10];System.out.println(f);} catch (ArrayIndexOutOfBoundsException var3) {var3.printStackTrace();}timer += System.currentTimeMillis();}}

通过查看字节码结构可以知道,首先我们需要增加一个field给Time类。然后在除了构造器以外的方法注入计时逻辑的字节码。我们先以第一个方法myCount()为例,用javap工具查看字节码信息如下:

public void myCount() throws java.lang.Exception;descriptor: ()Vflags: ACC_PUBLICCode:stack=7, locals=3, args_size=10: getstatic#18// Field timer:J3: invokestatic #24// Method java/lang/System.currentTimeMillis:()J6: lsub7: putstatic#18// Field timer:J10: iconst_511: istore_112: bipush1014: istore_215: getstatic#28// Field java/lang/System.out:Ljava/io/PrintStream;18: iload_219: iload_120: isub21: invokevirtual #34// Method java/io/PrintStream.println:(I)V24: getstatic#18// Field timer:J27: invokestatic #24// Method java/lang/System.currentTimeMillis:()J30: ladd31: putstatic#18// Field timer:J34: returnLocalVariableTable:Start Length Slot Name Signature10250 this Lasm/core/methord/Time;12231i I15202j ILineNumberTable:line 8: 10line 9: 12line 10: 15line 11: 24Exceptions:throws java.lang.Exception

从方法的偏移量0 到 7 是我们的 timer -=System.currentTimeMillis();对应的字节码实现。24 到31 是timer += System.currentTimeMillis();的字节码实现。基本可以判定,我们需要再方法刚进入的时候先生成timer -= System.currentTimeMillis();的字节码,,然后在方法返回return 指令或者是athrow指令之前生成timer+= System.currentTimeMillis()的字节码。

timer +=System.currentTimeMillis()我们可以通过visitCode(方法开始是通过此方法的调用)方法中添加ASM提供的字节码指令生成的几个方法来实现:

@Overridepublic void visitCode() {mv.visitCode();mv.visitFieldInsn(Opcodes.GETSTATIC, owner, "timer", "J");mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false);mv.visitInsn(Opcodes.LSUB);mv.visitFieldInsn(Opcodes.PUTSTATIC, owner, "timer", "J");}

timer -=System.currentTimeMillis()需要通过visitInsn(int opcode)方法来完成,遍历所有的操作码来判断我们当前的指令是否是return 或者athrow 。如果是那么前插入我们需要的指令,再继续调用下一层mv.visitInsn(opcode)。代码如下:

@Overridepublic void visitInsn(int opcode) {if ((opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN) || opcode == Opcodes.ATHROW) {mv.visitFieldInsn(Opcodes.GETSTATIC, owner, "timer", "J");mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false);mv.visitInsn(Opcodes.LADD);mv.visitFieldInsn(Opcodes.PUTSTATIC, owner, "timer", "J");}mv.visitInsn(opcode);} 那么最后还剩下,需要在class中生成一个timer的属性,如前面ClassVisitor的介绍一样,需要在ClassVisitor 的适配子类中的visitEnd()方法中插入我们的FieldVisitor。

@Overridepublic void visitEnd() {if (!isInterface) {FieldVisitor fv = cv.visitField(Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC, "timer", "J", null, null);if (fv != null) {fv.visitEnd();}}cv.visitEnd();} 至此,我们的字节码已经创建和生成完毕,为了健壮性考虑,我们只要再加上是否是Interface的判断,因为接口是没有方法实现体的,并且还要判断,构造器方法中不添加timer计时逻辑。这里我们把需要注入逻辑的Class的name通过参数owner传递给MethodVisitor。整体Adapter方法如下:

只要功夫深,铁棒磨成绣花针。

ASM(四) 利用Method 组件动态注入方法逻辑

相关文章:

你感兴趣的文章:

标签云: