前面的文章我们一直在讨论类加载器相关的知识,这篇文章我们谈一下,类加载器的实际应用热加载。说到热加载,做过线上项目的一定用过,下面我们来讨论一下热加载。
我们将讨论常见的两种形式的热加载:一种使用ClassLoader实现热加载的方式;另一种使用Instrumentation的方式。本文我们一起讨论ClassLoader如何实现热加载。
ClassLoader实现热加载
通过前面的文章我们知道了类加载机制是双亲委派模型,“同一个类名,同一个类加载器实例加载的,代表是同一个类”。如果我们要自己实现业务代码的热加载,就不能用默认的类加载器实例,因为同一个类加载器实例加载一次后会存起来,后面的class文件就算更新了也不会再次加载了。
我还是通过一个例子来说明
自定义类加载器
loadClass:加载类的入口方法,其中实现遵循了双亲委派机制,该方法返回Class实例;(最好不要破坏双亲委派机制)
findClass:loadClass方法中,如果其父类加载器无法加载指定类,则调用自身的findClass方法完成加载,该方法返回Class实例;
defineClass:接收以字节数组表示的类字节码,并把它转换成 Class 实例;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
public class MyClassLoader extends ClassLoader{
// 热加载文件的存放路径
private String classpath;
MyClassLoader(String classpath){
super(ClassLoader.getSystemClassLoader());
this.classpath = classpath;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] classData = loadClassData(name);
if (classData == null) {
return null;
}
// defineClass:接收以字节数组表示的类字节码,并把它转换成Class实例
return defineClass(null, classData, 0, classData.length);
}
private byte[] loadClassData(String name) {
try {
name = name.replaceAll("\\.", "//");
String path = classpath + name + ".class";
File file = new File(path);
if(!file.exists()) {
return null;
}
// 将class文件转化成字节数组
FileInputStream in = new FileInputStream(file);
ByteArrayOutputStream bao = new ByteArrayOutputStream();
int b = 0;
while ((b = in.read()) != -1) {
bao.write(b);
}
in.close();
return bao.toByteArray();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
这样我们自定义的类加载器就完成了,下面我们就可以进行测试了
定义热加载的类
public interface TestClass {
void test();
}
public class TestClassImpl implements TestClass {
@Override
public void test() {
System.out.println("after !!!!!");
}
}
public class Test {
private static final ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
public static void main(String[] args) {
scheduledExecutorService.scheduleAtFixedRate(Test::classLoaderTest, 1, 1, TimeUnit.SECONDS);
}
private static void classLoaderTest() {
try {
MyClassLoader myClassLoader = new MyClassLoader("D:/pch/");
Class<?> clazz = myClassLoader.findClass("com.pch.reload.TestClassImpl");
System.out.println("加载TestClassImpl热加载成功");
TestClass testClass = (TestClass) clazz.getConstructor().newInstance();
testClass.test();
} catch (Exception e) {
e.printStackTrace();
}
}
}
这样我们就能实现了热加载,例子比较简单,实际应用中,可以通过对外提供一个统一的调用,获得类实现。
下篇文章我们将要介绍一下Instrumentation方式实现热加载,这种方式也是我们项目中实际使用的方式。