本文建立在对instrumentation和agent有初步的了解的前提下阅读,关于这2个类的讲解在其它文章中。
这是一个maven项目,pom中需要的配置,lib中有asm的jar包
maven-compiler-plugin
3.7.0
1.8
lib
maven-jar-plugin
3.0.2
true
com.runtime.bytecode.UdAgent
打包后的agent的jar包中manifest属性如下:
Manifest-Version: 1.0
Premain-Class: com.runtime.bytecode.UdAgent
Built-By: lenovo
Created-By: Apache Maven 3.3.9
Build-Jdk: 1.8.0_121
agent类,只有一个方法,就是把自定义的类修改器添加到instrumentation中。
public class UdAgent {
public static void premain(String agentArgs, Instrumentation instrumentation){
instrumentation.addTransformer(new LogTransformer());
}
}
类转换器实现:
package com.runtime.bytecode.transformer;
import com.runtime.bytecode.adapter.TimeCountAdpter;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import java.io.IOException;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;
public class LogTransformer implements ClassFileTransformer{
@Override
public byte[] transform(ClassLoader loader, String className, Class> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
try {
ClassReader cr= new ClassReader(className);
ClassWriter cw=new ClassWriter(ClassWriter.COMPUTE_MAXS);
TimeCountAdpter timeCountAdpter=new TimeCountAdpter(cw);
cr.accept(timeCountAdpter,ClassReader.EXPAND_FRAMES);
return cw.toByteArray();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
实际修改字节码的方法,这里给每个类添加了一个字段UDASMCN,用于记录当前类的名字(方便打印信息)。同时记录每个方法的名字,以及执行时间。
package com.runtime.bytecode.adapter;
import org.objectweb.asm.*;
import org.objectweb.asm.commons.AnalyzerAdapter;
import org.objectweb.asm.commons.LocalVariablesSorter;
import javax.xml.ws.soap.MTOM;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
public class TimeCountAdpter extends ClassVisitor implements Opcodes {
private String owner;
private boolean isInterface;
private String filedName="UDASMCN";
private int acc=Opcodes.ACC_PUBLIC+Opcodes.ACC_STATIC+Opcodes.ACC_FINAL;
private boolean isPresent=false;
private String methodName;
public TimeCountAdpter(ClassVisitor classVisitor) {
super(ASM6, 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);
owner = 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);
if (!isInterface && mv != null && !name.equals("") && !name.equals("")) {
methodName=name;
AddTimerMethodAdapter at = new AddTimerMethodAdapter(mv);
at.aa = new AnalyzerAdapter(owner, access, name, descriptor, at);
at.lvs = new LocalVariablesSorter(access, descriptor, at.aa);
return at.lvs;
}
return mv;
}
public void visitEnd() {
if (!isInterface) {
FieldVisitor fv = cv.visitField(acc, filedName,
"Ljava/lang/String;", null, owner);
if (fv != null) {
fv.visitEnd();
}
}
cv.visitEnd();
}
class AddTimerMethodAdapter extends MethodVisitor {
private int time;
private int maxStack;
public LocalVariablesSorter lvs;
public AnalyzerAdapter aa;
public AddTimerMethodAdapter(MethodVisitor methodVisitor) {
super(ASM6, methodVisitor);
}
@Override
public void visitCode() {
mv.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", "", "()V", false);
mv.visitFieldInsn(GETSTATIC, owner, 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);
}
mv.visitInsn(opcode);
}
@Override
public void visitMaxs(int maxStack, int maxLocals) {
super.visitMaxs(Math.max(maxStack,this.maxStack), maxLocals);
}
}
}
打包成jar包后,在另一个程序启动时调用,启动参数如下:
-javaagent:D:\项目\bytecode\target\bytecode-1.0-SNAPSHOT.jar
执行效果:
时间单位是纳秒,可以看到每个方法执行完时,都会打印这个方法 的执行时间,以com/Main main:11457636为例,说明类com/Main的main方法执行力11毫秒。
java/util/concurrent/locks/AbstractQueuedSynchronizer doReleaseShared:821
java/util/concurrent/locks/AbstractQueuedSynchronizer releaseShared:55384
java/util/concurrent/CountDownLatch countDown:63590
main do else something
main 执行结束
com/Main main:11457636
agent原理