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默认并没有打开自动构建功能,这里需要手动开启,路径为:

java 热加载filter java 模块化 热加载_类加载


运行main方法,修改HelloServiceImpl实现,可以看到,控制台输出为修改后最新的内容。

java 热加载filter java 模块化 热加载_类加载_02