准备
我们引入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 {
@Override
public 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) {
@Override
public 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 {
@Override
public 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) {
@Override
public 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 {
@Override
public 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) {
@Override
public 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 {
@Override
public 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);
}
@Override
public 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;
}
@Override
public 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);
}
@Override
public 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;
}
@Override
public 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);
}
@Override
public 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;
}
@Override
public 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);
}
@Override
public void visitMaxs(int maxStack, int maxLocals) {
super.visitMaxs(Math.max(maxStack, this.maxStack), maxLocals);
}
}
}