JPDA#3:实现代码的HotSwap

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的。

参考资料

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值