0%

Java 类加载机制(五):自定义 ClassLoader 的应用:热部署

1. 什么是热部署

  • 不重启应用的情况下
  • 当类的定义即字节码文件修改后
  • 能够替换Class 创建的对象的过程

2. 写一个热部署的 Demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
//使用面向接口编程,定义一个接口 IHelloService。实现类是 shuo.laoma.dynamic.c87.HelloImpl,class 文件放到 MyClassLoader 的加载目录中
public interface IHelloService {
public void sayHello();
}

//演示类是 HotDeployDemo,它定义了以下静态变量:
private static final String CLASS_NAME = "shuo.laoma.dynamic.c87.HelloImpl"; //实现类名称
private static final String FILE_NAME = "data/c87/" + CLASS_NAME.replaceAll("\\.", "/") + ".class"; //具体的 class 文件路径
private static volatile IHelloService helloService; // IHelloService 实例

//当 CLASS_NAME 代表的类字节码改变后,我们希望重新创建 helloService,反映最新的代码
//用户端获取 IHelloService 的方法,这是一个单例模式
public static IHelloService getHelloService() {
if(helloService != null) {
return helloService;
}
synchronized(HotDeployDemo.class) {
if(helloService == null) {
helloService = createHelloService();
}
return helloService;
}
}

//createHelloService() 代码
private static IHelloService createHelloService() {
try {
MyClassLoader cl = new MyClassLoader();
Class<?> cls = cl.loadClass(CLASS_NAME); //使用 MyClassLoader 加载类
if(cls != null) {
return (IHelloService) cls.newInstance(); //利用反射创建实例,假定实现类有一个 public 无参构造方法
}
} catch(Exception e) {
e.printStackTrace();
}
return null;
}

//模拟一个客户端线程,它不停地获取 IHelloService 对象,并调用其方法,然后睡眠 1 秒钟
public static void client() {
Thread t = new Thread() {
@Override
public void run() {
try {
while(true) {
IHelloService helloService = getHelloService();
helloService.sayHello();
Thread.sleep(1000);
}
} catch(InterruptedException e) {

}
}
};
t.start();
}

//怎么知道类的 class 文件发生了变化,并重新创建 helloService 对象呢?我们使用一个单独的线程模拟这一过程
public static void monitor() {
Threat t = new Thread() {
private long lastModified = new File(FILE_NAME).lastModified(); //使用文件的最后修改时间来跟踪文件是否发生了变化
@Override
public void run() {
try {
while(true) {
Thread.sleep(100);
long now = new File(FILE_NAME).lastModified();
if(now != lastModified) {
lastModified = now;
reloadHelloService(); //当文件修改后,调用 reloadHelloService() 来重新加载
}
}
} catch(InterruptedException e) {

}
}
};
t.start();
}

//reloadHelloService()
public static void reloadHelloService() {
helloService = createHelloService(); //就是利用 MyClassLoader 重新创建 HelloService,创建后,赋值给 helloService,这样,下次 getHelloService() 获取到的就是最新的了
}

//在主程序中启动 client 和 monitor 线程
public static void main(String[] args) {
monitor();
client();
}

//在运行过程中,替换 HelloImpl.class,可以看到行为会变化。为便于演示,我们在 data/c7/shuo/laoma/dynamic/c87 目录下准备了两个不同的实现类:HelloImpl_origin.class 和 HelloImpl_revised.class,在运行过程中替换,会看到输出不一样。使用 cp 命令修改 HelloImpl.class,如果其内容与 `HelloImpl_origin.class` 一样,输出为:hello;如果与 HelloImpl_revised.class 一样,输出为:hello revised。
//完整的代码和数据在 github 上,地址为:https://github.com/swiftma/program-logic,位于包 shuo.laoma.dynamic.c87 下
-------------------- 本文结束感谢您的阅读 --------------------