Java类中热替换的概念、设计与实现(3)

    实现Java类的热替换

    现在来介绍一下我们的实验方法,为了简单起见,我们的包为默认包,没有层次,并且省去了所有错误处理。要替换的类为Foo,实现很简单,仅包含一个方法sayHello:

    1. public class Foo{  
    2.     public void sayHello() {  
    3.         System.out.println("hello world! (version one)");  
    4.     }  

    在当前工作目录下建立一个新的目录swap,把编译好的Foo.class文件放在该目录中。接下来要使用我们前面编写的HotswapCL来实现该类的热替换。具体的做法为:我们编写一个定时器任务,每隔2秒钟执行一次。其中,我们会创建新的类加载器实例加载Foo类,生成实例,并调用sayHello方法。接下来,我们会修改Foo类中sayHello方法的打印内容,重新编译,并在系统运行的情况下替换掉原来的Foo.class,我们会看到系统会打印出更改后的内容。定时任务的实现如下(其它代码省略,请读者自行补齐):

    1. public void run(){  
    2.     try {  
    3.         // 每次都创建出一个新的类加载器 
    4.         HowswapCL cl = new HowswapCL("../swap", new String[]{"Foo"});  
    5.         Class clcls = cl.loadClass("Foo");  
    6.         Object foo = cls.newInstance();  
    7.  
    8.         Method m = foo.getClass().getMethod("sayHello", new Class[]{});  
    9.         m.invoke(foo, new Object[]{});  
    10.      
    11.     }  catch(Exception ex) {  
    12.         ex.printStackTrace();  
    13.     }  

    编译、运行我们的系统,会出现如下的打印:

    热替换前的运行结果

    图3.热替换前的运行结果

    好,现在我们把Foo类的sayHello方法更改为:

    1. public void sayHello() {  
    2.     System.out.println("hello world! (version two)");  

    在系统仍在运行的情况下,编译,并替换掉swap目录下原来的Foo.class文件,我们再看看屏幕的打印,奇妙的事情发生了,新更改的类在线即时生效了,我们已经实现了Foo类的热替换。屏幕打印如下:

    热替换后的运行结果

    图4.热替换后的运行结果

    敏锐的读者可能会问,为何不用把foo转型为Foo,直接调用其sayHello方法呢?这样不是更清晰明了吗?下面我们来解释一下原因,并给出一种更好的方法。如果我们采用转型的方法,代码会变成这样:Foofoo=(Foo)cls.newInstance();读者如果跟随本文进行试验的话,会发现这句话会抛出ClassCastException异常,为什么吗?因为在Java中,即使是同一个类文件,如果是由不同的类加载器实例加载的,那么它们的类型是不相同的。在上面的例子中cls是由HowswapCL加载的,而foo变量类型声名和转型里的Foo类却是由run方法所属的类的加载器(默认为AppClassLoader)加载的,因此是完全不同的类型,所以会抛出转型异常。

    那么通过接口调用是不是就行了呢?我们可以定义一个IFoo接口,其中声名sayHello方法,Foo实现该接口。也就是这样:IFoofoo=(IFoo)cls.newInstance();本来该方法也会有同样的问题的,因为外部声名和转型部分的IFoo是由run方法所属的类加载器加载的,而Foo类定义中implementsIFoo中的IFoo是由HotswapCL加载的,因此属于不同的类型转型还是会抛出异常的,但是由于我们在实例化HotswapCL时是这样的:

    1. HowswapCLcl=newHowswapCL("../swap",newString[]{"Foo"});

    其中仅仅指定Foo类由HotswapCL加载,而其实现的IFoo接口文件会委托给系统类加载器加载,因此转型成功,采用接口调用的代码如下:

    1. public void run(){  
    2.     try {  
    3.         HowswapCL cl = new HowswapCL("../swap", new String[]{"Foo"});  
    4.         Class clcls = cl.loadClass("Foo");  
    5.         IFoo foo = (IFoo)cls.newInstance();  
    6.         foo.sayHello();  
    7.     } catch(Exception ex) {  
    8.         ex.printStackTrace();  
    9.     }  

    确实,简洁明了了很多,在我们的实验中,每当定时器调度到run方法时,我们都会创建一个新的HotswapCL实例,在产品代码中,无需如此,仅当需要升级替换时才去创建一个新的类加载器实例。

posted @ 2010-03-04 13:16  林强  阅读(166)  评论(0编辑  收藏  举报