之前的使用自定義類加載器實現熱修改:https://www.cnblogs.com/yuanyb/p/12066388.html
這兩天學習了java-agent,之前對這個就有興趣,一直想學習來着,昨天借着實習任務就學習了一下。
附上javassist文檔地址:http://www.javassist.org/tutorial/tutorial.html
java-agent 有兩種,分別是:JDK5引入的 premain,和 JDK6 引入的 agentmain,前者可以在 JVM 加載類之前攔截並修改字節碼,后者可以在運行時將 agent 附加到到任意的虛擬機中來修改字節碼,並且,修改后可以立馬更新,不需要重新加載類,因此可以實現熱修改,並且比自定義類加載器更方便。修改字節碼就得靠字節碼框架了,如 ASM,javassist。
例如,給已經運行的 Java 程序的某個類的方法添加耗時監測。先打包 agent(需要配置manifest),然后執行task的main方法來啟動一個虛擬機進程,最后執行Test的main方法動態修改Task類。
agent:
public class AgentMain { public static void agentmain(String agentArgs, Instrumentation inst) throws UnmodifiableClassException { System.out.println("agentmain"); inst.addTransformer(new Transformer(agentArgs), true); inst.retransformClasses(Task.class); // 允許修改Task類 } private static class Transformer implements ClassFileTransformer { private final String targetClassName; public Transformer(String targetClassName) { this.targetClassName = targetClassName; } public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { className = className.replaceAll("/", "."); if (!className.equals(targetClassName)) { return null; } System.out.println("transform: " + className); ClassPool classPool = ClassPool.getDefault(); classPool.appendClassPath(new LoaderClassPath(loader)); // 將要修改的類的classpath加入到ClassPool中,否則找不到該類 try { CtClass ctClass = classPool.get(className); for (CtMethod ctMethod : ctClass.getDeclaredMethods()) { if (Modifier.isPublic(ctMethod.getModifiers()) && !ctMethod.getName().equals("main")) { // 修改字節碼 ctMethod.addLocalVariable("begin", CtClass.longType); ctMethod.addLocalVariable("end", CtClass.longType); ctMethod.insertBefore("begin = System.currentTimeMillis();"); ctMethod.insertAfter("end = System.currentTimeMillis();"); ctMethod.insertAfter("System.out.println(\"方法" + ctMethod.getName() + "耗時\"+ (end - begin) +\"ms\");"); } } ctClass.detach(); return ctClass.toBytecode(); } catch (Exception e) { e.printStackTrace(); } return classfileBuffer; } } }
要被熱修改的類:
public class Task implements Runnable { @Override public void run() { System.out.println("111"); } public static void main(String[] args) throws Exception { Task task = new Task(); while (true) { TimeUnit.SECONDS.sleep(3); task.run(); } } }
將代理attach到某個虛擬機進程中:
public class Test { public static void main(String[] args) throws Exception { System.out.println("main start"); for (VirtualMachineDescriptor descriptor : VirtualMachine.list()) { if (descriptor.displayName().equals("test.Task")) { VirtualMachine virtualMachine = VirtualMachine.attach(descriptor.id()); virtualMachine.loadAgent("E:\\Programming\\java-agent\\target\\java-agent-1.0-SNAPSHOT.jar", "test.Task"); // 傳入agent的jar包路徑,test.Task是一個String agentArgs,就像main方法的String[] args,使用戶傳入的,test.Task表示要熱修改test.Task類 virtualMachine.detach(); } } } }