• 实现Java类的热替换

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

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

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

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

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

java ref的替代方案_类加载器

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

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

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

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

java ref的替代方案_java ref的替代方案_02

图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时是这样的:

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

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

public void run(){   
 
    try {   
 
        HowswapCL cl = new HowswapCL("../swap", new String[]{"Foo"});   
 
        Class clcls = cl.loadClass("Foo");   
 
        IFoo foo = (IFoo)cls.newInstance();   
 
        foo.sayHello();   
 
    } catch(Exception ex) {   
 
        ex.printStackTrace();   
 
    }   
 
}

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