准备
我们引入asm相关的依赖
<dependency><groupId>org.ow2.asm</groupId><artifactId>asm</artifactId><version>7.2</version></dependency><dependency><groupId>org.ow2.asm</groupId><artifactId>asm-commons</artifactId><version>7.2</version></dependency><dependency><groupId>org.ow2.asm</groupId><artifactId>asm-util</artifactId><version>7.2</version></dependency>
先往代码里插入一句话
假定我们要在代码入口处插入如下语句
System.out.println("hello, i come in ...");
我们先看一下对应的asm代码
此时,我们就可以照抄写好的代码了
package com.deer.agent.sandbox;import org.objectweb.asm.*;import java.lang.instrument.ClassFileTransformer;import java.lang.instrument.IllegalClassFormatException;import java.security.ProtectionDomain;public class AgentClazzTransformer implements ClassFileTransformer {@Overridepublic byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {if(className!=null && className.startsWith("com/deer/base/service")){System.out.println("I can see you ...."+className);//ClassReader cr = new ClassReader(classfileBuffer);ClassWriter cw = new ClassWriter(cr,ClassWriter.COMPUTE_FRAMES|ClassWriter.COMPUTE_MAXS);cr.accept(new ClassVisitor(Opcodes.ASM7) {@Overridepublic MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {System.out.println("name: "+name +",descriptor: "+descriptor+",signature: "+signature);MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");mv.visitLdcInsn("hello, i come in ...");mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);return mv;}},ClassWriter.COMPUTE_MAXS);return cw.toByteArray();}return classfileBuffer;}}
我们访问页面后结果如下
多次重复访问后如下
为什么没有按照预期执行
我们看上述截图,需要打印输出到console控制台的,没有输出,在方法访问的时候,加个日志,结果只执行了一遍构造函数的逻辑(
跳过构造函数
package com.deer.agent.sandbox;import org.objectweb.asm.*;import java.lang.instrument.ClassFileTransformer;import java.lang.instrument.IllegalClassFormatException;import java.security.ProtectionDomain;public class AgentClazzTransformer implements ClassFileTransformer {@Overridepublic byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {if(className!=null && className.startsWith("com/deer/base/service")){System.out.println("I can see you ...."+className);//ClassReader cr = new ClassReader(classfileBuffer);ClassWriter cw = new ClassWriter(cr,ClassWriter.COMPUTE_FRAMES|ClassWriter.COMPUTE_MAXS);cr.accept(new ClassVisitor(Opcodes.ASM7) {@Overridepublic MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {System.out.println("name: "+name +",descriptor: "+descriptor+",signature: "+signature);MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);//普通构造函数和静态构造函数if(name.equals("<init>") || name.equals("<clinit>")){return mv;}mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");mv.visitLdcInsn("hello, i come in ...");mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);return mv;}},ClassWriter.COMPUTE_MAXS);return cw.toByteArray();}return classfileBuffer;}}
计算方法耗时
package com.deer.agent.sandbox;import org.objectweb.asm.*;import java.lang.instrument.ClassFileTransformer;import java.lang.instrument.IllegalClassFormatException;import java.security.ProtectionDomain;public class AgentClazzTransformer implements ClassFileTransformer {@Overridepublic byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {if(className!=null && className.startsWith("com/deer/base/service")){System.out.println("I can see you ...."+className);//ClassReader cr = new ClassReader(classfileBuffer);ClassWriter cw = new ClassWriter(cr,ClassWriter.COMPUTE_FRAMES|ClassWriter.COMPUTE_MAXS);cr.accept(new ClassVisitor(Opcodes.ASM7) {@Overridepublic MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {System.out.println("name: "+name +",descriptor: "+descriptor+",signature: "+signature);long start = System.nanoTime();MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);//普通构造函数和静态构造函数if(name.equals("<init>") || name.equals("<clinit>")){return mv;}long delta = System.nanoTime()-start;System.out.println("cost...."+delta);mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");mv.visitLdcInsn("hello, i come in ...");mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);return mv;}},ClassWriter.COMPUTE_MAXS);return cw.toByteArray();}return classfileBuffer;}}
完成预期
我们观察以上输出结果,和预期相差比较远,但是思路和方向是对的,我们可以进一步进行思路整理,这里给每个类添加了一个字段ASM_TIME_COST,用于记录当前类的名字(方便打印信息)。同时记录每个方法的名字,以及执行时间。我们需要学习相关MethodVisitor的使用方法。完整代码如下
package com.deer.agent.sandbox;import org.objectweb.asm.ClassReader;import org.objectweb.asm.ClassWriter;import java.lang.instrument.ClassFileTransformer;import java.lang.instrument.IllegalClassFormatException;import java.security.ProtectionDomain;public class AgentClazzTransformer implements ClassFileTransformer {@Overridepublic byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {ClassReader cr = new ClassReader(classfileBuffer);ClassWriter cw = new ClassWriter(cr,ClassWriter.COMPUTE_MAXS);TimeCoster timeCoster = new TimeCoster(cw);cr.accept(timeCoster,ClassReader.EXPAND_FRAMES);return cw.toByteArray();}}
package com.deer.agent.sandbox;import org.objectweb.asm.*;import org.objectweb.asm.commons.AnalyzerAdapter;import org.objectweb.asm.commons.LocalVariablesSorter;public class TimeCoster extends ClassVisitor implements Opcodes {private String clazzName;private String methodName;private boolean isInterface;private boolean isPresent = false;private String filedName = "ASM_TIME_COST";public TimeCoster(ClassVisitor classVisitor) {super(ASM7, classVisitor);}@Overridepublic void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {super.visit(version, access, name, signature, superName, interfaces);clazzName = name;isInterface = (access & ACC_INTERFACE) != 0;}@Overridepublic FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) {if (name.equals(filedName)) {isPresent = true;}return super.visitField(access, name, descriptor, signature, value);}@Overridepublic MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {MethodVisitor mv = cv.visitMethod(access, name, descriptor, signature, exceptions);//跳过java原生相关的类if (!clazzName.startsWith("com/deer/base")) {return mv;}if (!isInterface && mv != null && !name.equals("<init>") && !name.equals("<clinit>")) {methodName = name;TimerMethod timerMethod = new TimerMethod(mv);timerMethod.aa = new AnalyzerAdapter(clazzName, access, name, descriptor, timerMethod);timerMethod.lvs = new LocalVariablesSorter(access, descriptor, timerMethod.aa);return timerMethod.lvs;}return mv;}@Overridepublic void visitEnd() {if (!isInterface) {FieldVisitor fv = cv.visitField(Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC + Opcodes.ACC_FINAL, filedName, "Ljava/lang/String;", null, clazzName);if (fv != null) {fv.visitEnd();}}super.visitEnd();}class TimerMethod extends MethodVisitor {public LocalVariablesSorter lvs;public AnalyzerAdapter aa;private int time;private int maxStack;public TimerMethod(MethodVisitor methodVisitor) {super(ASM7, methodVisitor);}@Overridepublic void visitCode() {super.visitCode();mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "nanoTime", "()J", false);time = lvs.newLocal(Type.LONG_TYPE);mv.visitVarInsn(LSTORE, time);maxStack = 4;}@Overridepublic void visitInsn(int opcode) {if (((opcode >= IRETURN && opcode <= RETURN) || opcode == ATHROW) && !isPresent) {mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "nanoTime", "()J", false);mv.visitVarInsn(LLOAD, time);mv.visitInsn(LSUB);mv.visitVarInsn(LSTORE, time);mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");mv.visitTypeInsn(NEW, "java/lang/StringBuilder");mv.visitInsn(DUP);mv.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V", false);mv.visitFieldInsn(GETSTATIC, clazzName, filedName, "Ljava/lang/String;");mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);mv.visitLdcInsn(" " + methodName + ":");mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);mv.visitVarInsn(LLOAD, time);mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(J)Ljava/lang/StringBuilder;", false);mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false);mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);maxStack = Math.max(aa.stack.size() + 4, maxStack);}super.visitInsn(opcode);}@Overridepublic void visitMaxs(int maxStack, int maxLocals) {super.visitMaxs(Math.max(maxStack, this.maxStack), maxLocals);}}}

