1 类加载原理
Java类的加载过程主要分为三个步骤,加载、链接、初始化,其中将类加载到JVM中的工作由类加载器完成。在加载阶段,类加载器可以从不同的数据源(jar文件、class文件、网络文件)读取 Java 字节代码,并转换成 java.lang.Class类的一个实例。
在Java中,主要提供有四种类加载器,引导类加载器(BootStrapClassLoader)、扩展类加载器(ExtClassLoader)、应用类加载器(AppClassLoader) 以及用户自定义的 ClassLoader,这四种类加载器有着明显的层级关系(由顶至下)(引导类加载器->扩展类加载器->应用类加载器->自定义加载器),其中引导类加载器用来加载Java的核心库,比如加载 jre/lib 下面的 jar 文件,扩展类加载器负责加载我们放到 jre/lib/ext/ 目录下面的 jar 包,而应用类加载器则加载系统变量classpath路径下 的内容,当然,我们也可以实现自定义的类加载器,自己控制和管理类的加载过程。
另外,这四种类加载器在加载类的时候会遵循一个称作“双亲委派”的准则,在加载类的时候,会首先委派其父加载器进行加载,如果已经加载则直接返回该类的引用,这是一个递归的过程。如果到最顶层也没有加载到指定类,那么会自顶向下挨个尝试加载,直到用户自定义类加载器,如果还不能成功,就会抛出异常。
2 类的热加载基本实现
热加载指的是,在不停止正在运行的系统的情况下进行类的替换,前面我们知道了类的加载由类加载器完成。热加载是类加载常见的应用,比如Tomcat中jsp的热替换。下面我们自定义类加载器实现简单的类的热加载。
2.1 自定义类加载器
对于类加载器,有几个重要的方法:
loadClass:加载类的入口方法,其中实现遵循了双亲委派机制,该方法返回Class实例;
findClass:loadClass方法中,如果其父类加载器无法加载指定类,则调用自身的findClass方法完成加载,该方法返回Class实例;
defineClass:接收以字节数组表示的类字节码,并把它转换成 Class 实例;
public class MyClassLoader extends ClassLoader{
private String classpath;
public MyClassLoader(String classpath){
this.classpath = classpath;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] classdata = loadClassData(name);
return this.defineClass(name, classdata, 0, classdata.length);
}
private byte[] loadClassData(String name) {
try {
name = name.replace(".", "//");
String path = classpath + name + ".class";
File file = new File(path);
FileInputStream is = new FileInputStream(file);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int b = 0;
while ((b = is.read()) != -1) {
baos.write(b);
}
is.close();
return baos.toByteArray();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
2.2 热加载实现
我们首先试着自定义一个测试接口,并由自定义类加载器完成接口实现类的热替换:
public interface HelloService {
void sayHello();
}
热替换实现,这里依赖于接口,不依赖于具体:
public class Main {
private static final ScheduledExecutorService scheduledExecutorService = Executors.
newScheduledThreadPool(1);
public static void main(String[] args) {
classLoaderTest();
}
public static void classLoaderTest(){
Class<?> clazz = null;
try {
MyClassLoader myClassLoader = new MyClassLoader(
"D:/Workspaces/IdeaProjects/classloader-java-class/out/production/classloader-java-class/");
clazz = myClassLoader.loadClass("gdou.laixiaoming.HelloServiceImpl");
System.out.println("加载HelloService实现类成功,由"+clazz.getClassLoader() + "加载");
HelloService helloService = (HelloService)clazz.getConstructor(new Class[]{}).newInstance(new Object[]{});
helloService.sayHello();
} catch (Exception e) {
e.printStackTrace();
}
scheduledExecutorService.schedule(() -> {
classLoaderTest();
}, 1 , TimeUnit.SECONDS);
}
}
另外再新建一个Java项目,实现前面的测试接口,主要用于测试实现类的自动编译,拿到得自动生成的class文件:
public class HelloServiceImpl implements HelloService {
@Override
public void sayHello() {
System.out.println("Hello,ClassLoader!");
}
}
这里要注意一下,IDEA默认并没有打开自动构建功能,这里需要手动开启,路径为:
运行main方法,修改HelloServiceImpl实现,可以看到,控制台输出为修改后最新的内容。