曹工说Spring Boot源码(25)-- Spring注解扫描的瑞士军刀,ASM + Java Instrumentation,顺便提提Jar包破解 (4)

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。

曹工说Spring Boot源码(25)-- Spring注解扫描的瑞士军刀,ASM + Java Instrumentation,顺便提提Jar包破解

我们这里的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这么简单的话,那我要从梦里笑醒。

为啥是假的,因为:真正的代码,是长下面这样的:

曹工说Spring Boot源码(25)-- Spring注解扫描的瑞士军刀,ASM + Java Instrumentation,顺便提提Jar包破解

看到这里,是不是想溜了,这都啥玩意,看不懂啊,不过不要着急,办法总比困难多。

类转换器的真正实现方法

我们先装个idea插件,叫:asm-bytecode-outline。这个插件的作用,简而言之,就是帮你把java代码翻译成ASM的写法。在线装不了的,可以离线装:

asm-bytecode-outline

装好插件后,只要在我们的TimeAdviceAdapter类,点右键:

曹工说Spring Boot源码(25)-- Spring注解扫描的瑞士军刀,ASM + Java Instrumentation,顺便提提Jar包破解

就会生成我们需要的ASM代码,然后拷贝:

曹工说Spring Boot源码(25)-- Spring注解扫描的瑞士军刀,ASM + Java Instrumentation,顺便提提Jar包破解

什么时候拷贝结束呢?

1585318732857

基本上,这样就可以了。

填坑指南

作为一个常年掉坑的人,我在这个坑里也摸爬了整整一天。

大家可以看到,我们的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字段。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpzfzd.html