自定义类加载器实现热部署
使用自定义的类加载器,在运行时,对字节码进行重新加载,生成新的对象。
实现效果
运行时,启动线程每秒执行对象方法,启动另外一个线程监听是否修改文件。
修改源文件后,自行重新编译运行
实现思路
Ⅰ. 将java源文件编译为class文件
Ⅱ. 自定义类加载器,读取class文件字节,并加载到内存中
Ⅲ. 读取加载器生成的Class对象
Ⅳ. 通过反射使用Class对象
实现过程
- 将java源文件编译为class文件,在当前目录下直接生成的class文件,使用编译器API来编译源码代替javac
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
compiler.run(null, null, null, FILEPATH);
- 自定义类加载器,重写findClass方法,通过IO读取class文件的字节数组,并使用defineClass方法进行加载
public class MyClassLoad extends ClassLoader {
private byte[] classByteArr;
private String classPath;
public MyClassLoad(ClassLoader parent) {
super(parent);//指定父类加载器
}
@Override
protected Class<?> findClass(String name) {
try {
classByteArr = readFileToByteArray(classPath);
} catch (IOException e) {
e.printStackTrace();
}
return defineClass(name, classByteArr, 0, classByteArr.length);
}
public static byte[] readFileToByteArray(String classFilePath) throws IOException {
File directory = new File(classFilePath);
InputStream inputStream = new FileInputStream(directory);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try {
byte[] buffer = new byte[1024];
int bytesRead = 0;
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
return outputStream.toByteArray();
} finally {
inputStream.close();
}
}
public void setClassPath(String classPath) {
this.classPath = classPath;
}
}
- 获取对象时,使用单例模式
private static Object get() throws IllegalAccessException, InstantiationException {
//使用单例模式
if (helloObject != null) {
return helloObject;
}
synchronized (Object.class) {
if (helloObject == null) {
setHelloClass();
return helloClass.newInstance();
}
}
return helloObject;
}
private static void setHelloClass() {
//生成class文件
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
compiler.run(null, null, null, FILEPATH);
System.out.println("生成class文件成功");
//获取自定义类加载器
MyClassLoad myClassLoad = new MyClassLoad(null);//指定父类加载器,null为Booststrap类加载器
//设置读取class文件路径
myClassLoad.setClassPath(CLASSPATH);
try {
//获取对象的class对象
helloClass = myClassLoad.loadClass("classLoad.HelloImp");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
- 设置两个线程,一个执行方法,一个监听文件是否修改
//每5秒执行对象的方法
Thread t1 = new Thread(() -> {
while (true) {
try {
helloObject = get();
helloClass.getMethod("sayHello").invoke(helloObject);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t1.start();
//监听文件是否被修改
Thread t2 = new Thread(() -> {
long lastModified = new File(FILEPATH).lastModified();
while (true) {
try {
Thread.sleep(1000);
long now = new File(FILEPATH).lastModified();
if (now != lastModified) {
lastModified = now;
helloObject = null;//将对象设置为null,另外一个线程执行方法时,会自动获取最新的对象
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t2.start();
小结
使用自定义类加载器时,需要重写findClass方法。且在构造方法中指定父类加载器,默认不设置时,会使用当前的类加载器,根据双亲委派机制,我们写的自定义类加载器会委托上一层加载器AppClassLoader进行类的加载。
这时,我们定义的类加载器findClass方法没有作用。如果指定父加载器为null,即Booststrap加载器,当找不到我们要加载的类时,才会委托我们自定义的类加载器加载。