javaasm怎么注⼊_ASM(四)利⽤Method组件动态注⼊⽅法
逻辑
这篇继续结合样例来深⼊了解下Method组件动态变更⽅法字节码的实现。通过前⾯⼀篇,知道ClassVisitor 的visitMethod()⽅法能够返回⼀个MethodVisitor的实例。
那么我们也基本能够知道,同ClassVisitor改变类成员⼀样,MethodVIsistor假设须要改变⽅法成员,注⼊逻辑,也能够通过继承MethodVisitor,来编写⼀个MethodXXXAdapter来实现对于⽅法逻辑的注⼊。通过以下的两个样例来介绍下⽆状态注⼊和有状态注⼊⽅法逻辑的实现。
样例主要參考官⽅⽂档介绍,⼤家依照这个思路能够扩展很多其它种场景的应⽤。
⼀、⽆状态注⼊
先看⼀个样例,也是⽐較常见的⼀种场景,我们须要给以下这个类的全部⽅法注⼊⼀个计时的逻辑。
源代码例如以下:
ethord;
/
**
* 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)
//
ethord;
public class Time {
java怎么编写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: ()V
flags: ACC_PUBLIC
Code:
stack=7, locals=3, args_size=1
0: getstatic #18 // Field timer:J
3: invokestatic #24 // Method java/lang/System.currentTimeMillis:()J
6: lsub
7: putstatic #18 // Field timer:J
10: iconst_5
11: istore_1
12: bipush 10
14: istore_2
15: getstatic #28 // Field java/lang/System.out:Ljava/io/PrintStream;
18: iload_2
19: iload_1
20: isub
21: invokevirtual #34 // Method java/io/PrintStream.println:(I)V
24: getstatic #18 // Field timer:J
27: invokestatic #24 // Method java/lang/System.currentTimeMillis:()J
30: ladd
31: putstatic #18 // Field timer:J
34: return
LocalVariableTable:
Start Length Slot Name Signature
10 25 0 this Lasm/core/methord/Time;
12 23 1 i I
15 20 2 j I
LineNumberTable:
line 8: 10
line 9: 12
line 10: 15
line 11: 24
Exceptions:
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提供的字节码指令⽣成的⼏个⽅法来实现:
@Override
public 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)。
代码例如以下:
@Override
public 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。
@Override
public 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⽅法例如以下:
ethord;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
/**
* Created by yunshen.ljy on 2015/6/29.
*/
public class AddTimerAdapter extends ClassVisitor {
private String owner;
private boolean isInterface;
public AddTimerAdapter(ClassVisitor cv) {
super(Opcodes.ASM4, cv);
}
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
cv.visit(version, access, name, signature, superName, interfaces);
owner = name;
isInterface = (access & Opcodes.ACC_INTERFACE) != 0;
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);
if (!isInterface && mv != null && !name.equals("")) {
mv = new AddTimerMethodAdapter(mv);
}
return mv;
}
@Override
public void visitEnd() {
if (!isInterface) {
FieldVisitor fv = cv.visitField(Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC, "timer", "J", null, null);
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论