ASM(动态)
前面那篇是静态修改字节码的方式,这种方式只是一种demo,方便初学者进行学习基本的API操作,真正在线上环境有作用的是动态的去修改字节码。
例如一个进程正在运行着,这个时候我们需要在业务无感知的情况下进行打印日志,或者计算接口的操作时间这种操作,这个时候我们就需要动态的去操作JVM中的字节码文件了
动态操作字节码
动态操作字节码的方式,其实还是主要使用agentmain
,在javaagent的类attch到jvm上面的时候会调用这个方法执行内部的代码逻辑。
如何使用
首先我们需要有一个agent,这个agent实现了agentmain的方法,并且改写对应类的字节码文件。DynamicPreMainAddTimeStatAgent
public class DynamicPreMainAddTimeStatAgent {
public static void agentmain(String agentArgs, Instrumentation inst) throws Exception {
System.out.println("Agent Main called");
System.out.println("agentArgs:" + agentArgs);
inst.addTransformer(new ClassFileTransformer() {
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
System.out.println("load transform");
if (className.equals("com/wsqandgy/asm/dynamic/Account")) {
System.out.println("meet com/wsqandgy/asm/dynamic/Account ");
/** classfileBuffer */
ClassReader classReader = new ClassReader(classfileBuffer);
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
TimeStatClassAdapter timeStatClassAdapter = new TimeStatClassAdapter(cw);
classReader.accept(timeStatClassAdapter, ClassReader.SKIP_DEBUG);
byte[] data = cw.toByteArray();
return data;
} else {
System.out.println("load class:" + className);
System.out.println("new !");
return classfileBuffer;
}
}
},true);
System.out.println(Account.class.getClassLoader());
inst.retransformClasses(Account.class);
}
}
在上述操作中使用了TimeStatClassAdapter
这个Class修改适配器和TimeStatMethodAdapter
修改适配器。
增加TimeStat的方法,提供打印运行时间的方法。
public class TimeStat {
static ThreadLocal<Long> t = new ThreadLocal<Long>();
public static void start() {
t.set(System.currentTimeMillis());
}
public static void end() {
long time = System.currentTimeMillis();
System.out.print(Thread.currentThread().getStackTrace()[2] + " spend:");
System.out.println(time - t.get());
}
}
针对上面的内容创建MANIFEST.MF文件,并且进行打成jar包。
Manifest-Version: 1.0
Agent-Class: com.wsqandgy.asm.dynamic.DynamicPreMainAddTimeStatAgent
Can-Redefine-Classes: true
Can-Retransform-Classes: true
以上就是全部改写字节码文件的类和agentmain对应方法的类文件了。众所周知,我们需要对JVM进行注入就需要获取到全部的JVM运行信息,Java提供了一个方法可以获取到运行的JVM的相关信息。List<VirtualMachineDescriptor> list = VirtualMachine.list();
我们通过以上的方法获取到对应的JVM运行实例,进行attch操作。
public class AttachToolMain {
public static void main(String[] args) throws Exception {
List<VirtualMachineDescriptor> list = VirtualMachine.list();
for (VirtualMachineDescriptor machineDescriptor : list) {
if (machineDescriptor.displayName().equals("com.wsqandgy.asm.dynamic.RunLoopAccountMain")) {
VirtualMachine virtualMachine = VirtualMachine.attach(machineDescriptor.id());
System.out.println(new File("/Users/gongyan/Documents/home_code/tools/classes/artifacts/dynamicTimeAgent/dynamicTimeAgent.jar").exists());
virtualMachine.loadAgent("/Users/gongyan/Documents/home_code/tools/classes/artifacts/dynamicTimeAgent/dynamicTimeAgent.jar","argument for agent");
System.out.println("attach ok!");
virtualMachine.detach(); // 派遣操作,意思为生效
}
}
}
}
运行效果:
before当我们运行AttachToolMain后,字节码重新生成已经增加了对应的响应时间了。
after
网友评论