Java实现class文件热替换

classloader

java的classloader是实现将java文件编译后产生的字节码加载进内存当中,最终成为可被java虚拟机直接使用的java类型。而原生的java虚拟机中的classloader,如果我们想直接实现class文件的热替换,是不可能的,因为java虚拟机双亲委派模型的限制;因此,为了实现我们目标——class文件热替换,必须实现一个我们自己的classloader

my classloader

要实现自己的classloader,首先要继承java自有的ClassLoader,重写父类的findClass(String name)方法,由前面可知,classloader是把.class文件读取加载进内存中,因此,我们可以写一个关于.class文件的file读取函数,将文件的内容读出并转为byte[]数组,然后返回这个字节数组即可。

监听文件变化

要实现class文件热替换,那就要实现热这个关键。因此,就需要不断去监听class文件的变化情况,一旦class文件发生改变,立即通知我们自己创建的classloader去重新加载这个.class文件。然后覆盖原本内存中的class文件。从而实现我们热的关键

代码实现

自定义的 ClassLoader

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
public class MyClassLoader extends ClassLoader {

private File file;

public File getFile() {
return file;
}

public void setFile(File file) {
this.file = file;
}

protected MyClassLoader(ClassLoader parent) {
super(parent);
}

@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
return super.loadClass(name);
}

@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
return super.loadClass(name, resolve);
}

@Override
protected Class<?> findClass(String name) {
Class clazz = null;
try {
byte[] data = getClassFileBytes(getFile());
clazz = defineClass(name, data, 0, data.length);
if (null == clazz) {}
} catch (Exception e) {
e.printStackTrace();
}
return clazz;
}

private byte[] getClassFileBytes(File file) throws Exception {
FileInputStream fis = new FileInputStream(file);
FileChannel fileC = fis.getChannel();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
WritableByteChannel outC = Channels.newChannel(baos);
ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
while (true) {
int i = fileC.read(buffer);
if (i == 0 || i == -1) {
break;
}
buffer.flip();
outC.write(buffer);
buffer.clear();
}
fis.close();
return baos.toByteArray();
}

@Override
public String toString() {
return super.toString() + new Date().toLocaleString();
}
}

测试

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
public class Main {

Class aClass;

private void init() {
new Thread() {
long lastModify = 0;
@Override
public void run() {
while (true) {
File f = new File("/media/tensor/resource/code/IdeaProjects/spring-demo/hot-swap/out/production/classes/com/hotswap/org/Test.class");
if (lastModify != f.lastModified()) {
lastModify = f.lastModified();
MyClassLoader myClassLoader = new MyClassLoader(this.getContextClassLoader());
myClassLoader.setFile(f);
try {
Class<Test> clazz = (Class<Test>) myClassLoader.findClass("com.hotswap.org.Test");
aClass = clazz;
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
}

public static void main(String[] args) throws InterruptedException, NoSuchMethodException, IllegalAccessException,
InvocationTargetException, InstantiationException {
Main m = new Main();
m.init();
while (true) {
if (m.aClass != null) {
Method method = m.aClass.getMethod("log", null);
Object o = m.aClass.getConstructor(new Class[]{}).newInstance(new Object[]{});
method.invoke(o, null);
}
Thread.sleep(5 * 1000);
}
}

}