24.5 自定义ClassLoader的应用:热部署
24.5 自定义ClassLoader的应用:热部署
所谓热部署,就是在不重启应用的情况下,当类的定义即字节码文件修改后,能够替换该Class创建的对象,怎么做到这一点呢?我们利用MyClassLoader,看个简单的示例。
我们使用面向接口的编程,定义一个接口IHelloService:
1 | public interface IHelloService { |
实现类是shuo.laoma.dynamic.c87.HelloImpl, class文件放到MyClassLoader的加载目录中。
演示类是HotDeployDemo,它定义了以下静态变量:
1 | private static final String CLASS_NAME = "shuo.laoma.dynamic.c87.HelloImpl"; |
CLASS_NAME表示实现类名称,FILE_NAME是具体的class文件路径,helloService是IHelloService实例。
当CLASS_NAME代表的类字节码改变后,我们希望重新创建helloService,反映最新的代码,怎么做呢?先看用户端获取IHelloService的方法:
1 | public static IHelloService getHelloService() { |
这是一个单例模式,createHelloService()的代码为:
1 | private static IHelloService createHelloService() { |
它使用MyClassLoader加载类,并利用反射创建实例,它假定实现类有一个public无参构造方法。
在调用IHelloService的方法时,客户端总是先通过getHelloService获取实例对象,我们模拟一个客户端线程,它不停地获取IHelloService对象,并调用其方法,然后睡眠1秒钟,其代码为:
1 | public static void client() { |
怎么知道类的class文件发生了变化,并重新创建helloService对象呢?我们使用一个单独的线程模拟这一过程,代码为:
1 | public static void monitor() { |
我们使用文件的最后修改时间来跟踪文件是否发生了变化,当文件修改后,调用reloadHelloService()来重新加载,其代码为:
1 | public static void reloadHelloService() { |
就是利用MyClassLoader重新创建HelloService,创建后,赋值给helloService,这样,下次getHelloService()获取到的就是最新的了。
在主程序中启动client和monitor线程,代码为:
1 | public static void main(String[] args) { |
在运行过程中,替换HelloImpl.class,可以看到行为会变化,为便于演示,我们在data/c87/shuo/laoma/dynamic/c87/目录下准备了两个不同的实现类:HelloImpl_origin.class和HelloImpl_revised. class,在运行过程中替换,会看到输出不一样,如图24-1所示。
使用cp命令修改HelloImpl.class,如果其内容与HelloImpl_origin.class一样,输出为”hello”;如果与HelloImpl_revised.class一样,输出为”hello revised”。
完整的代码和数据在github上,地址为https://github.com/swiftma/program-logic ,位于包shuo.laoma.dynamic.c87下。
本章介绍了Java中的类加载机制,包括Java加载类的基本过程,类ClassLoader的用法,以及如何创建自定义的ClassLoader,探讨了两个简单应用示例,一个通过动态加载实现了可配置的策略,另一个通过自定义ClassLoader实现了热部署。
需要说明的是,Java 9引入了模块的概念。在模块化系统中,类加载的过程有一些变化,扩展类的目录被删除掉了,原来的扩展类加载器没有了,增加了一个平台类加载器(Platform Class Loader),角色类似于扩展类加载器,它分担了一部分启动类加载器的职责,另外,加载的顺序也有一些变化,限于篇幅,我们就不探讨了。
从第21章到本章,我们探讨了Java中的多个动态特性,包括反射、注解、动态代理和类加载器,作为应用程序员,大部分用得都比较少,用得较多的就是使用框架和库提供的各种注解了,但这些特性大量应用于各种系统程序、框架和库中,理解这些特性有助于我们更好地理解它们,也可以在需要的时候自己实现动态、通用、灵活的功能。
在注解一章,我们提到,注解是一种声明式编程风格,它提高了Java语言的表达能力,日常编程中一种常见的需求是文本处理,在计算机科学中,有一种技术大大提高了文本处理的表达能力,那就是正则表达式,大部分编程语言都有对它的支持,它有什么强大功能呢?让我们下一章探讨。