ASM(動態(tài))
前面那篇是靜態(tài)修改字節(jié)碼的方式昔期,這種方式只是一種demo硼一,方便初學(xué)者進行學(xué)習(xí)基本的API操作,真正在線上環(huán)境有作用的是動態(tài)的去修改字節(jié)碼哼蛆。
例如一個進程正在運行著,這個時候我們需要在業(yè)務(wù)無感知的情況下進行打印日志叠洗,或者計算接口的操作時間這種操作,這個時候我們就需要動態(tài)的去操作JVM中的字節(jié)碼文件了
動態(tài)操作字節(jié)碼
動態(tài)操作字節(jié)碼的方式玉锌,其實還是主要使用agentmain
禀倔,在javaagent的類attch到j(luò)vm上面的時候會調(diào)用這個方法執(zhí)行內(nèi)部的代碼邏輯。
如何使用
首先我們需要有一個agent鞋既,這個agent實現(xiàn)了agentmain的方法跌前,并且改寫對應(yīng)類的字節(jié)碼文件抵乓。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());
}
}
針對上面的內(nèi)容創(chuàng)建MANIFEST.MF文件酌儒,并且進行打成jar包。
Manifest-Version: 1.0
Agent-Class: com.wsqandgy.asm.dynamic.DynamicPreMainAddTimeStatAgent
Can-Redefine-Classes: true
Can-Retransform-Classes: true
以上就是全部改寫字節(jié)碼文件的類和agentmain對應(yīng)方法的類文件了榴啸。眾所周知,我們需要對JVM進行注入就需要獲取到全部的JVM運行信息库说,Java提供了一個方法可以獲取到運行的JVM的相關(guān)信息。List<VirtualMachineDescriptor> list = VirtualMachine.list();
我們通過以上的方法獲取到對應(yīng)的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(); // 派遣操作歇由,意思為生效
}
}
}
}
運行效果:
當我們運行AttachToolMain后,字節(jié)碼重新生成已經(jīng)增加了對應(yīng)的響應(yīng)時間了。