在该类加载器的实现中,所有指定必须由它直接加载的类都在该加载器实例化时进行了加载,当通过loadClass进行类的加载时,如果该类没有加载过,并且不属于必须由该类加载器加载之列都委托给系统加载器进行加载。
实现Java类的热替换
现在来介绍一下我们的实验方法,为了简单起见,我们的包为默认包,没有层次,并且省去了所有错误处理。要替换的类为Foo,实现很简单,仅包含一个方法sayHello:
1.public class Foo{
2. public void sayHello() {
3. System.out.println("hello world! (version one)");
4. }
5.}
在当前工作目录下建立一个新的目录swap,把编译好的Foo.class文件放在该目录中。接下来要使用我们前面编写的HotswapCL来实现该类的热替换。具体的做法为:我们编写一个定时器任务,每隔2秒钟执行一次。其中,我们会创建新的类加载器实例加载Foo类,生成实例,并调用sayHello方法。接下来,我们会修改Foo类中sayHello方法的打印内容,重新编译,并在系统运行的情况下替换掉原来的Foo.class,我们会看到系统会打印出更改后的内容。定时任务的实现如下(其它代码省略,请读者自行补齐):
6.public void run(){
7. try {
8. // 每次都创建出一个新的类加载器
9. HowswapCL cl = new HowswapCL("../swap", new String[]{"Foo"});
10. Class clcls = cl.loadClass("Foo");
11. Object foo = cls.newInstance();
12.
13. Method m = foo.getClass().getMethod("sayHello", new Class[]{});
14. m.invoke(foo, new Object[]{});
15.
16. } catch(Exception ex) {
17. ex.printStackTrace();
18. }
19.}
编译、运行我们的系统,会出现如下的打印:
图3.热替换前的运行结果
好,现在我们把Foo类的sayHello方法更改为:
20.public void sayHello() {
21. System.out.println("hello world! (version two)");
22.}
在系统仍在运行的情况下,编译,并替换掉swap目录下原来的Foo.class文件,我们再看看屏幕的打印,奇妙的事情发生了,新更改的类在线即时生效了,我们已经实现了Foo类的热替换。屏幕打印如下:
图4.热替换后的运行结果
敏锐的读者可能会问,为何不用把foo转型为Foo,直接调用其sayHello方法呢?这样不是更清晰明了吗?下面我们来解释一下原因,并给出一种更好的方法。如果我们采用转型的方法,代码会变成这样:Foofoo=(Foo)cls.newInstance();读者如果跟随本文进行试验的话,会发现这句话会抛出ClassCastException异常,为什么吗?因为在Java中,即使是同一个类文件,如果是由不同的类加载器实例加载的,那么它们的类型是不相同的。在上面的例子中cls是由HowswapCL加载的,而foo变量类型声名和转型里的Foo类却是由run方法所属的类的加载器(默认为AppClassLoader)加载的,因此是完全不同的类型,所以会抛出转型异常。