自定义类加载器实现代码热替换

代码热替换,在不重启服务器的情况下可以修改类的代码并使之生效

1. 自定义类加载器

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
package com.pibgstar.demo.java;

import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Set;

/**
* @author pibigstar
* @desc 代码热替换,在不重启服务器的情况下可以修改类的代码并使之生效
**/
public class MyClassLoader extends ClassLoader{
// 定义.class路径
private String swapPath;
// 存储哪些类需要我自身去加载
private Set<String> myselfLoader;

public MyClassLoader(String swapPath, Set<String> myselfLoader) {
this.swapPath = swapPath;
this.myselfLoader = myselfLoader;
}

@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
Class<?> loadedClass = findLoadedClass(name);
// 需要我自己去加载
if (loadedClass==null && myselfLoader.contains(name)) {
loadedClass = findClass(name);
if (loadedClass!=null){
return loadedClass;
}
}
return super.loadClass(name, resolve);
}

@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
// 根据文件系统路径加载class文件,并返回byte数组
byte[] b = getClassByte(name);
// 调用ClassLoader提供的方法,将二进制数组转换成Class类的实例
return defineClass(name,b,0,b.length);
}

private byte[] getClassByte(String name) {
String className = name.substring(name.lastIndexOf(".")+1,
name.length()) + ".class";
try {
FileInputStream fis = new FileInputStream(swapPath + className);

ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buff = new byte[1024];
int length = 0;
while ((length=fis.read(buff))>0){
baos.write(buff,0,length);
}
return baos.toByteArray();
} catch (IOException e) {
e.printStackTrace();
}
return new byte[]{};
}
}

2. 测试

自定义一个每2秒打印调用一次方法打印版本号,先编译一次版本号为2的 MyClassLoaderTest .class类,然后启动版本号为1的,在运行过程中,我们把版本号为1的MyClassLoaderTest .class类替换成版本号为2的MyClassLoaderTest .class类,我们发现控制台立马输出了版本号2

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
package com.pibgstar.demo.java;
import com.google.common.collect.Sets;
import java.util.Timer;
import java.util.TimerTask;
/**
* @author pibigstar
* @desc
**/
public class MyClassLoaderTest {
public void printVersion() {
System.out.println("版本号1");
}
public static void main(String[] args){
new Timer().schedule(new TimerTask() {
@Override
public void run() {
// target/classes/com/pibgstar/demo/java/swap/
String swapPath = MyClassLoader.class.getResource("").getPath();
String className = "com.pibgstar.demo.java.MyClassLoaderTest";
// 创建自定义的类加载器,将路径和需要我们自己去加载的类名传递进去
MyClassLoader myClassLoader = new MyClassLoader(swapPath, Sets.newHashSet(className));
try {
//使用自定义的ClassLoader加载类,并调用printVersion方法。
Object o = myClassLoader.loadClass(className).newInstance();
o.getClass().getMethod("printVersion").invoke(o);
} catch (Exception e) {
e.printStackTrace();
}
}
},0,2000);
}
}

3. 结果截图

-------------本文结束感谢您的阅读-------------