1. 程式人生 > 實用技巧 >使用java-agent的agentmain實現熱修改

使用java-agent的agentmain實現熱修改

之前的使用自定義類載入器實現熱修改: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();
            }
        }
    }
}