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

参考资料

时间: 2016-04-06

JPDA#3:实现代码的HotSwap的相关文章

PayPal曝远程代码执行漏洞(含视频)

日前知名在线支付公司PayPal被曝存在严重的远程代码执行漏洞,攻击者可以利用该漏洞在PayPal的web应用服务器上执行恶意命令,最终获得服务器控制权限. 漏洞描述 这个远程代码执行漏洞由独立安全研究员Milan A Solanki发现,被Vulnerability Lab评为严重,通用漏洞评分系统(CVSS)分数达到了9.3,漏洞影响了PayPal的在线营销web应用服务器. 该漏洞存在于服务器中的Java调试线协议(Java Debug Wire Protocol, JDWP),攻击者可以

诊断 Java 代码:设计轻松的代码维护

设计 本月,Eric Allen 解释了在使代码更易于维护的同时,避免和控制无理由的变化怎么会是保持代码健壮性的关键.他集中讨论了诸如函数样式代码编写之类的概念,以及标记字段.方法和类的方法来处理并防止可变性.Eric 还解释了本任务中单元测试和重构的角色,并提供了协助实现重构的两个工具.在相关论坛中与作者和其他读者分享您对本文的看法.(您也可以单击本文顶部或底部的"讨论",访问该论坛.)有效调试源自良好的编程.设计易于维护的程序是程序员面临的最困难挑战之一,其部分原因在于程序通常并不

php单文件版在线代码编辑器

 这篇文章主要介绍了php单文件版在线代码编辑器,个人感觉相当不错,分享给大家,需要的朋友可以参考下     密码加密方式: * md5(自设密码+$ace) //$ace为cdn镜像地址 使用方法: * 1.确认 $pwd 变量值为 false, 上传本文件到PHP空间并访问 * 2.第一次访问提示设置密码,设置密码并牢记 * 3.使用第一次设置的密码登录后,默认编辑的是本php文件, * 4.本文件是编辑器核心文件,请不要随意修改 * 5.保存编辑的文件请用 Ctrl + S 按键组合,等待

PHP运行SVN命令显示某用户的文件更新记录代码

 使用SVN开发者们平时开发或代码上线过程中需要知道某个时间段内修改或添加过那些文件,所以用PHP写了个小程序,直接在浏览器中调用即可   代码如下: <?php $user=trim($_GET['user']); $d=$_GET['date']; if(!$d){  $d=date('Ymd',time()-86400*14); } if(empty($user)){  echo "例如:svn_log.php?user=wang&date=20130118";  

php Calender(日历)代码

 这篇文章主要介绍了php Calender(日历)代码,有需要的朋友可以参考一下 代码如下:    代码如下: <?php /**  *   * 我的日历  * date_default_timezone_set date mktime  * @param int $year  * @param int $month  * @param string $timezone  * @author fc_lamp  */ function myCalender($year = '', $month

PHP抓屏函数实现屏幕快照代码分享

 谁说抓图只能用QQ.用打印屏幕,PHP也能做到,本文主要介绍PHP抓屏函数实现屏幕快照的方法   代码如下: <?php * 屏幕快照 $im = imagegrabscreen(); imagepng($im, "myscreenshot.png");     * 抓取一个窗口(IE为例)   $browser = new COM("InternetExplorer.Application"); $handle = $browser->HWND; $

微信扫描二维码登录网站代码

 用户通过扫描网页提供的二维码实现登陆信息获取,大家参考使用吧 请先下载  snoopy 类   代码如下: <?php /**  *  微信公众平台PHP-SDK  *  Wechatauth为非官方微信登陆API  *  用户通过扫描网页提供的二维码实现登陆信息获取  *  主要实现如下功能:  *  get_login_code() 获取登陆授权码, 通过授权码才能获取二维码  *  get_code_image($code='') 将上面获取的授权码转换为图片二维码  *  verify

如何用C#写代码批量下载网页上提供的附件。

问题描述 如何用C#写代码批量下载网页上提供的附件. 在一个网页上有一个导出PDF文件的功能,是当点击这个按钮后,执行一个脚本,然后弹出文件下载另存为的对话框进文件的下载功能. 现在希望用C#实现自动批量的下载并保存这些PDF文件. 求实现方法,谢谢! 解决方案 用webclient.downloadfile或者httpwebrequest去下载. 解决方案二: 建议压缩成ZIP包后再下载. 解决方案三: 执行的是一个脚本,现在我可以实现通过代码模拟这个单击操作,执行这个脚本,但是如何能得到下载

android 邮件-android通过代码获取当前系统中安装的所有邮件应用列表

问题描述 android通过代码获取当前系统中安装的所有邮件应用列表 想通过代码得到当前系统所有可以收发邮件的应用,通过列表显示出来,求大神指导,有知道的朋友请知道下获取的方法,目前我能想到的就是获取当前所有安装的应用通过关键字过滤,不过感觉这种方法不是很好,不能保证所有过滤出来的都是邮件应用.