准备

我们引入asm相关的依赖

  1. <dependency>
  2. <groupId>org.ow2.asm</groupId>
  3. <artifactId>asm</artifactId>
  4. <version>7.2</version>
  5. </dependency>
  6. <dependency>
  7. <groupId>org.ow2.asm</groupId>
  8. <artifactId>asm-commons</artifactId>
  9. <version>7.2</version>
  10. </dependency>
  11. <dependency>
  12. <groupId>org.ow2.asm</groupId>
  13. <artifactId>asm-util</artifactId>
  14. <version>7.2</version>
  15. </dependency>

先往代码里插入一句话

假定我们要在代码入口处插入如下语句

  1. System.out.println("hello, i come in ...");

我们先看一下对应的asm代码
image.png
此时,我们就可以照抄写好的代码了

  1. package com.deer.agent.sandbox;
  2. import org.objectweb.asm.*;
  3. import java.lang.instrument.ClassFileTransformer;
  4. import java.lang.instrument.IllegalClassFormatException;
  5. import java.security.ProtectionDomain;
  6. public class AgentClazzTransformer implements ClassFileTransformer {
  7. @Override
  8. public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
  9. if(className!=null && className.startsWith("com/deer/base/service")){
  10. System.out.println("I can see you ...."+className);
  11. //
  12. ClassReader cr = new ClassReader(classfileBuffer);
  13. ClassWriter cw = new ClassWriter(cr,ClassWriter.COMPUTE_FRAMES|ClassWriter.COMPUTE_MAXS);
  14. cr.accept(new ClassVisitor(Opcodes.ASM7) {
  15. @Override
  16. public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
  17. System.out.println("name: "+name +",descriptor: "+descriptor+",signature: "+signature);
  18. MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
  19. mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
  20. mv.visitLdcInsn("hello, i come in ...");
  21. mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
  22. return mv;
  23. }
  24. },ClassWriter.COMPUTE_MAXS);
  25. return cw.toByteArray();
  26. }
  27. return classfileBuffer;
  28. }
  29. }

我们访问页面后结果如下
image.png
多次重复访问后如下
image.png

为什么没有按照预期执行

我们看上述截图,需要打印输出到console控制台的,没有输出,在方法访问的时候,加个日志,结果只执行了一遍构造函数的逻辑(),这是怎么了?

跳过构造函数

  1. package com.deer.agent.sandbox;
  2. import org.objectweb.asm.*;
  3. import java.lang.instrument.ClassFileTransformer;
  4. import java.lang.instrument.IllegalClassFormatException;
  5. import java.security.ProtectionDomain;
  6. public class AgentClazzTransformer implements ClassFileTransformer {
  7. @Override
  8. public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
  9. if(className!=null && className.startsWith("com/deer/base/service")){
  10. System.out.println("I can see you ...."+className);
  11. //
  12. ClassReader cr = new ClassReader(classfileBuffer);
  13. ClassWriter cw = new ClassWriter(cr,ClassWriter.COMPUTE_FRAMES|ClassWriter.COMPUTE_MAXS);
  14. cr.accept(new ClassVisitor(Opcodes.ASM7) {
  15. @Override
  16. public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
  17. System.out.println("name: "+name +",descriptor: "+descriptor+",signature: "+signature);
  18. MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
  19. //普通构造函数和静态构造函数
  20. if(name.equals("<init>") || name.equals("<clinit>")){
  21. return mv;
  22. }
  23. mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
  24. mv.visitLdcInsn("hello, i come in ...");
  25. mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
  26. return mv;
  27. }
  28. },ClassWriter.COMPUTE_MAXS);
  29. return cw.toByteArray();
  30. }
  31. return classfileBuffer;
  32. }
  33. }

image.png

计算方法耗时

  1. package com.deer.agent.sandbox;
  2. import org.objectweb.asm.*;
  3. import java.lang.instrument.ClassFileTransformer;
  4. import java.lang.instrument.IllegalClassFormatException;
  5. import java.security.ProtectionDomain;
  6. public class AgentClazzTransformer implements ClassFileTransformer {
  7. @Override
  8. public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
  9. if(className!=null && className.startsWith("com/deer/base/service")){
  10. System.out.println("I can see you ...."+className);
  11. //
  12. ClassReader cr = new ClassReader(classfileBuffer);
  13. ClassWriter cw = new ClassWriter(cr,ClassWriter.COMPUTE_FRAMES|ClassWriter.COMPUTE_MAXS);
  14. cr.accept(new ClassVisitor(Opcodes.ASM7) {
  15. @Override
  16. public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
  17. System.out.println("name: "+name +",descriptor: "+descriptor+",signature: "+signature);
  18. long start = System.nanoTime();
  19. MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
  20. //普通构造函数和静态构造函数
  21. if(name.equals("<init>") || name.equals("<clinit>")){
  22. return mv;
  23. }
  24. long delta = System.nanoTime()-start;
  25. System.out.println("cost...."+delta);
  26. mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
  27. mv.visitLdcInsn("hello, i come in ...");
  28. mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
  29. return mv;
  30. }
  31. },ClassWriter.COMPUTE_MAXS);
  32. return cw.toByteArray();
  33. }
  34. return classfileBuffer;
  35. }
  36. }

image.png

完成预期

我们观察以上输出结果,和预期相差比较远,但是思路和方向是对的,我们可以进一步进行思路整理,这里给每个类添加了一个字段ASM_TIME_COST,用于记录当前类的名字(方便打印信息)。同时记录每个方法的名字,以及执行时间。我们需要学习相关MethodVisitor的使用方法。完整代码如下

  1. package com.deer.agent.sandbox;
  2. import org.objectweb.asm.ClassReader;
  3. import org.objectweb.asm.ClassWriter;
  4. import java.lang.instrument.ClassFileTransformer;
  5. import java.lang.instrument.IllegalClassFormatException;
  6. import java.security.ProtectionDomain;
  7. public class AgentClazzTransformer implements ClassFileTransformer {
  8. @Override
  9. public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
  10. ClassReader cr = new ClassReader(classfileBuffer);
  11. ClassWriter cw = new ClassWriter(cr,ClassWriter.COMPUTE_MAXS);
  12. TimeCoster timeCoster = new TimeCoster(cw);
  13. cr.accept(timeCoster,ClassReader.EXPAND_FRAMES);
  14. return cw.toByteArray();
  15. }
  16. }
  1. package com.deer.agent.sandbox;
  2. import org.objectweb.asm.*;
  3. import org.objectweb.asm.commons.AnalyzerAdapter;
  4. import org.objectweb.asm.commons.LocalVariablesSorter;
  5. public class TimeCoster extends ClassVisitor implements Opcodes {
  6. private String clazzName;
  7. private String methodName;
  8. private boolean isInterface;
  9. private boolean isPresent = false;
  10. private String filedName = "ASM_TIME_COST";
  11. public TimeCoster(ClassVisitor classVisitor) {
  12. super(ASM7, classVisitor);
  13. }
  14. @Override
  15. public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
  16. super.visit(version, access, name, signature, superName, interfaces);
  17. clazzName = name;
  18. isInterface = (access & ACC_INTERFACE) != 0;
  19. }
  20. @Override
  21. public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) {
  22. if (name.equals(filedName)) {
  23. isPresent = true;
  24. }
  25. return super.visitField(access, name, descriptor, signature, value);
  26. }
  27. @Override
  28. public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
  29. MethodVisitor mv = cv.visitMethod(access, name, descriptor, signature, exceptions);
  30. //跳过java原生相关的类
  31. if (!clazzName.startsWith("com/deer/base")
  32. ) {
  33. return mv;
  34. }
  35. if (!isInterface && mv != null && !name.equals("<init>") && !name.equals("<clinit>")) {
  36. methodName = name;
  37. TimerMethod timerMethod = new TimerMethod(mv);
  38. timerMethod.aa = new AnalyzerAdapter(clazzName, access, name, descriptor, timerMethod);
  39. timerMethod.lvs = new LocalVariablesSorter(access, descriptor, timerMethod.aa);
  40. return timerMethod.lvs;
  41. }
  42. return mv;
  43. }
  44. @Override
  45. public void visitEnd() {
  46. if (!isInterface) {
  47. FieldVisitor fv = cv.visitField(Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC + Opcodes.ACC_FINAL, filedName, "Ljava/lang/String;", null, clazzName);
  48. if (fv != null) {
  49. fv.visitEnd();
  50. }
  51. }
  52. super.visitEnd();
  53. }
  54. class TimerMethod extends MethodVisitor {
  55. public LocalVariablesSorter lvs;
  56. public AnalyzerAdapter aa;
  57. private int time;
  58. private int maxStack;
  59. public TimerMethod(MethodVisitor methodVisitor) {
  60. super(ASM7, methodVisitor);
  61. }
  62. @Override
  63. public void visitCode() {
  64. super.visitCode();
  65. mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "nanoTime", "()J", false);
  66. time = lvs.newLocal(Type.LONG_TYPE);
  67. mv.visitVarInsn(LSTORE, time);
  68. maxStack = 4;
  69. }
  70. @Override
  71. public void visitInsn(int opcode) {
  72. if (((opcode >= IRETURN && opcode <= RETURN) || opcode == ATHROW) && !isPresent) {
  73. mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "nanoTime", "()J", false);
  74. mv.visitVarInsn(LLOAD, time);
  75. mv.visitInsn(LSUB);
  76. mv.visitVarInsn(LSTORE, time);
  77. mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
  78. mv.visitTypeInsn(NEW, "java/lang/StringBuilder");
  79. mv.visitInsn(DUP);
  80. mv.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V", false);
  81. mv.visitFieldInsn(GETSTATIC, clazzName, filedName, "Ljava/lang/String;");
  82. mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
  83. mv.visitLdcInsn(" " + methodName + ":");
  84. mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
  85. mv.visitVarInsn(LLOAD, time);
  86. mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(J)Ljava/lang/StringBuilder;", false);
  87. mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false);
  88. mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
  89. maxStack = Math.max(aa.stack.size() + 4, maxStack);
  90. }
  91. super.visitInsn(opcode);
  92. }
  93. @Override
  94. public void visitMaxs(int maxStack, int maxLocals) {
  95. super.visitMaxs(Math.max(maxStack, this.maxStack), maxLocals);
  96. }
  97. }
  98. }