码迷,mamicode.com
首页 > 其他好文 > 详细

JPDA(三):实现代码的HotSwap

时间:2015-03-04 01:06:54      阅读:162      评论:0      收藏:0      [点我收藏+]

标签:jpda   hotswap   

JPDA系列:
1. JPDA(一):使用JDI写一个调试器
2. JPDA(二):架构源码浅析

redefineClasses

JPDA提供了一个API,VirtualMachine#redefineClasses,我们可以通过这个API来实现Java代码的热替换。

下面直接上代码,我们的目标VM运行了如下代码,前面已经说过,目标VM启动时需要添加option,-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8787

public class Main {

    public static void main(String[] args) throws Exception {
        Random random = new Random();
        while(true) {
            int i = random.nextInt(1000);
            if(i % 10 == 0) {
                new Foo().bar();
                Thread.sleep(5000);
            }
        }
    }

}
public class Foo {
    public void bar() {
        System.out.println("hello Foo.");
    }
}

我们要实现的代码Hot Swap就是,直接在线修改Foo#bar方法,使该方法输出hello HotSwapper.也相当于是热部署的功能了。下面是作为debugger的HotSwapper的代码,

import com.sun.jdi.Bootstrap;
import com.sun.jdi.ReferenceType;
import com.sun.jdi.VirtualMachine;
import com.sun.jdi.connect.Connector;
import com.sun.tools.jdi.SocketAttachingConnector;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;

public class HotSwapper {

    public static void main(String[] args) throws Exception{
        List<Connector> connectors =
                Bootstrap.virtualMachineManager().allConnectors();
        SocketAttachingConnector sac = null;
        for (Connector connector : connectors) {
            if(connector instanceof SocketAttachingConnector) {
                sac = (SocketAttachingConnector)connector;
            }
        }
        if(sac != null) {
            Map<String, Connector.Argument> defaultArguments = sac.defaultArguments();
            Connector.Argument hostArg = defaultArguments.get("hostname");
            Connector.Argument portArg = defaultArguments.get("port");
            hostArg.setValue("localhost");
            portArg.setValue("8787");
            VirtualMachine vm = sac.attach(defaultArguments);

            List<ReferenceType> rtList = vm.classesByName("me.kisimple.just4fun.Foo");
            ReferenceType rt = rtList.get(0);
            Map<ReferenceType, byte[]> newByteCodeMap = new HashMap<ReferenceType, byte[]>(1);
            byte[] newByteCode = genNewByteCode();
            newByteCodeMap.put(rt, newByteCode);

            if(vm.canRedefineClasses()) {
                vm.redefineClasses(newByteCodeMap);
            }
        }
    }

}

要使用VirtualMachine#redefineClasses方法,需要拿到要替换的Java类的字节码,由栗子中的genNewByteCode方法输出。下面介绍两种方式来完成,

  1. 使用Java Compiler API
  2. 使用Javassist

JavaCompiler

Java Compiler API 使用方式如下,

    private static byte[] genNewByteCodeUsingJavaCompiler() throws Exception {
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();

//        compiler.run(null, null, null, "E:\\Projects\\just4fun\\src\\main\\java\\me\\kisimple\\just4fun\\Foo.java");

        File javaFile = 
                new File("E:\\Projects\\just4fun\\src\\main\\java\\me\\kisimple\\just4fun\\Foo.java");
        StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
        Iterable<? extends JavaFileObject> compilationUnit =
                fileManager.getJavaFileObjectsFromFiles(Arrays.asList(javaFile));
        compiler.getTask(null, fileManager, null, null, null, compilationUnit).call();

        File classFile = 
                new File("E:\\Projects\\just4fun\\src\\main\\java\\me\\kisimple\\just4fun\\Foo.class");
        InputStream in = new FileInputStream(classFile);
        byte[] buf = new byte[(int)classFile.length()];
        while (in.read(buf) != -1) {}
        return buf;
    }

使用这种方式我们需要先修改Foo的源码,

public class Foo {
    public void bar() {
        System.out.println("hello HotSwapper.");
    }
}

然后运行HotSwapper就会使用JavaCompiler将修改后的源码重新编译,生成新的Foo.class文件,再使用文件IO的API读入class文件就达到我们的目的了。然后我们就可以看到目标VM的输出如下,

Listening for transport dt_socket at address: 8787
hello Foo.
hello Foo.
hello Foo.
Listening for transport dt_socket at address: 8787
hello HotSwapper.
hello HotSwapper.

妥妥的实现了代码的Hot Swap,或者说是热部署。
在将class文件读入到字节数组时,有个地方需要注意一下,byte[] buf = new byte[(int)classFile.length()];字节数组的大小不可以随便定义,不然会出现以下错误,目标VM会误以为整个字节数组都是class文件的字节码,

Exception in thread "main" java.lang.ClassFormatError: class not in class file format
    at com.sun.tools.jdi.VirtualMachineImpl.redefineClasses(VirtualMachineImpl.java:321)

Javassist

Javassist的API使用起来要简单得多,

    private static byte[] genNewByteCodeUsingJavassist() throws Exception {
        ClassPool pool = ClassPool.getDefault();
        CtClass cc = pool.get("me.kisimple.just4fun.Foo");
        CtMethod cm = cc.getDeclaredMethod("bar");
        cm.setBody("{System.out.println(\"hello HotSwapper.\");}");
        return cc.toBytecode();
    }

使用这种方式我们也不需要去修改Foo的源文件。

HotSwapper

其实在Javassist中已经实现了一个HotSwapper了,通过源码也能看到,它也是使用了JPDA的API来实现Hot Swap的。

参考资料

JPDA(三):实现代码的HotSwap

标签:jpda   hotswap   

原文地址:http://blog.csdn.net/kisimple/article/details/43835807

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!