4处,返回writer的字节码,给jvm;jvm使用该字节码,去redefine一个class出来
类转换器的具体实现 public static class TimeClassVisitor extends ClassVisitor { public TimeClassVisitor(ClassVisitor classVisitor) { super(Opcodes.ASM6, classVisitor); } // 1 @Override public MethodVisitor visitMethod(int methodAccess, String methodName, String methodDesc, String signature, String[] exceptions) { MethodVisitor methodVisitor = cv.visitMethod(methodAccess, methodName, methodDesc, signature, exceptions); // 2 return new TimeAdviceAdapter(Opcodes.ASM6, methodVisitor, methodAccess, methodName, methodDesc); } } }1处,visitMethod方法,会返回一个MethodVisitor,ASM会拿着我们返回的methodVisitor,去访问当前这个方法
2处,new了一个适配器,TimeAdviceAdapter。
我们这里的TimeAdviceAdapter,主要是希望在方法执行前后做点事,类似于切面,所以继承了一个AdviceAdapter,这个AdviceAdaper,帮我们实现了MethodVisitor的全部方法,我们只需要覆写我们想要覆盖的方法即可。
比如,AdviceAdaper,因为继承了MethodVisitor,其visitCode方法,会在访问方法体时被回调:
@Override public void visitCode() { super.visitCode(); // 1 onMethodEnter(); } //2 protected void onMethodEnter() {}1处,回调本来的onMethodEnter,是一个空实现,就是留给子类去重写的。
2处,可以看到,空实现。
所以,我们最终的TimeAdviceAdaper,代码如下:
public static class TimeAdviceAdapter extends AdviceAdapter { private String methodName; protected TimeAdviceAdapter(int api, MethodVisitor methodVisitor, int methodAccess, String methodName, String methodDesc) { super(api, methodVisitor, methodAccess, methodName, methodDesc); this.methodName = methodName; } @Override protected void onMethodEnter() { //在方法入口处植入 if ("<init>".equals(methodName)|| "<clinit>".equals(methodName)) { return; } String className = getClass().getName(); String s = className + "." + methodName; TimeHolder.start(s); } @Override protected void onMethodExit(int i) { //在方法出口植入 if ("<init>".equals(methodName) || "<clinit>".equals(methodName)) { return; } String className = getClass().getName(); String s = className + "." + methodName; long cost = TimeHolder.cost(s); System.out.println(s + ": " + cost); } }这份代码看着可还行?可惜啊,是假的,是错误的!写asm这么简单的话,那我要从梦里笑醒。
为啥是假的,因为:真正的代码,是长下面这样的:
看到这里,是不是想溜了,这都啥玩意,看不懂啊,不过不要着急,办法总比困难多。
类转换器的真正实现方法我们先装个idea插件,叫:asm-bytecode-outline。这个插件的作用,简而言之,就是帮你把java代码翻译成ASM的写法。在线装不了的,可以离线装:
asm-bytecode-outline
装好插件后,只要在我们的TimeAdviceAdapter类,点右键:
就会生成我们需要的ASM代码,然后拷贝:
什么时候拷贝结束呢?
基本上,这样就可以了。
填坑指南作为一个常年掉坑的人,我在这个坑里也摸爬了整整一天。
大家可以看到,我们的java写的方法里,是这样的:
@Override protected void onMethodEnter() { //在方法入口处植入 if ("<init>".equals(methodName)|| "<clinit>".equals(methodName)) { return; } String className = getClass().getName(); // 1. String s = className + "." + methodName; TimeHolder.start(s); }1处,访问了本地field,methodName
所以,asm也帮我们贴心地生成了这样的语句:
mv.visitFieldInsn(Opcodes.GETFIELD, "org/xunche/agent/TimeAgentByJava$TimeAdviceAdapter", "methodName", "Ljava/lang/String;");看起来就像是说,访问org/xunche/agent/TimeAgentByJava$TimeAdviceAdapter类的methodName字段。