重写类加载器实现简单热加载

1,877 阅读5分钟

Java ClassLoader简单讲解

java中,类的实例化主要包括两个部分:类的加载和类的实例化。类的加载又被分为显示加载和隐式加载,使用new关键字创建实例时候,其实隐式的包含了类的加载过程。显示类加载主要常用的是class.forName方法。但是他们本身都是通过调用ClassLoader类的loadClass方法来完成类的加载工作。

  1. JVM预定三种类型的类加载器:
  • 启动(BootStrap)类加载器:是用本地代码实现的类装载器,它负责加载 <Java_Runtime_Home>/lib下面的类库加载到内存中(如rt.jar)。由于启动类加载器设计到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不允许直接通过引用来操作。
  • 扩展(Extension)类加载器:是由 Sun 的 ExtClassLoader(sun.misc.Launcher$ExtClassLoader)实现的。它负责将< Java_Runtime_Home >/lib/ext或者由系统变量 java.ext.dir指定位置中的类库加载到内存中。开发者可以直接使用标准扩展类加载器。
  • 应用(Application)类加载器:是由 Sun 的 AppClassLoader(sun.misc.Launcher$AppClassLoader)实现的。它负责将系统类路径(CLASSPATH)中指定的类库加载到内存中。开发者可以直接使用系统类加载器。
  1. 双亲委派机制:
  • 当特定的类加载器接受到加载类的请求的时候,首先查找类是否已经加载,如果未加载则将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务,则返回;如果无法加载则由自己去加载。
  • 对于每个类加载器都有自己的命名空间,对于同一个类加载器的实例来说,每个名字相同的类只能存在一个,并且加载一次。也就是说对比一个类是否相同还要对比他们是否是由同一个类加载器的同一个实例加载(JVM自带的类加载器是采用单例模式,可以保证是由同一个实例加载)。
  1. ClassLoader类介绍
方法名称 作用
getParent() 返回该类加载器的父类加载器
loadClass(String name) 加载名称为 name的类,返回的结果是 java.lang.Class类的实例。
findClass(String name) 查找名称为 name的类,返回的结果是 java.lang.Class类的实例。
findLoadedClass(String name) 查找名称为 name的已经被加载过的类,返回的结果是 java.lang.Class类的实例。
defineClass(String name, byte[] b, int off, int len) 把字节数组 b中的内容转换成 Java 类,返回的结果是 java.lang.Class类的实例。这个方法被声明为 final的。
resolveClass(Class<?> c) 链接指定的 Java 类。

编写自定义类加载器

我们编写的类加载器需要继承自ClassLoader类,我们先了解有关热替换的一些重要的方法。

  • findLoadedClass:每个类加载器都维护有自己的一份已加载类名字空间,其中不能出现两个同名的类。凡是通过该类加载器加载的类,无论是直接的还是间接的,都保存在自己的名字空间中,该方法就是在该名字空间中寻找指定的类是否已存在,如果存在就返回给类的引用,否则就返回 null。这里的直接是指,存在于该类加载器的加载路径上并由该加载器完成加载,间接是指,由该类加载器把类的加载工作委托给其他类加载器完成类的实际加载。
  • getSystemClassLoader:Java2 中新增的方法。该方法返回系统使用的 ClassLoader。可以在自己定制的类加载器中通过该方法把一部分工作转交给系统类加载器去处理。
  • defineClass:该方法是 ClassLoader 中非常重要的一个方法,它接收以字节数组表示的类字节码,并把它转换成 Class 实例,该方法转换一个类的同时,会先要求装载该类的父类以及实现的接口类。
  • loadClass: 加载类的入口方法,调用该方法完成类的显式加载。通过对该方法的重新实现,我们可以完全控制和管理类的加载过程。
  • resolveClass:链接一个指定的类。这是一个在某些情况下确保类可用的必要方法,详见 Java 语言规范中“执行”一章对该方法的描述。

实现源码如下

package net.ziruo.classloader;

import java.io.*;
import java.util.HashSet;

/**
 * @Author: october
 * @Date: 2019/10/25 16:48
 * @Description:
 */
public class HotClassLoader extends ClassLoader {

    private String rootDir;

    private HashSet<String> classNameSet;

    public HotClassLoader(String rootDir, String[] classNames){
        this.rootDir = rootDir;
        classNameSet = new HashSet<>();
        loadAllClass(classNames);
    }

    // 加载所有类
    private void loadAllClass(String[] classNames){
        for (int i = 0; i < classNames.length; i++) {
            String className = classNames[i];
            String fullName = getFullName(className);
            try {
                loadClassToMetaSpace(className, fullName);
            } catch (IOException e) {
                e.printStackTrace();
            }
            classNameSet.add(className);
        }
    }

    // 得到类的完整名字=所在目录+包+文件名
    private String getFullName(String className){
    return rootDir + "\\" +
                className.replace(".", "\\") +
                ".class";
    }

    // 加载数据到元空间
    private Class loadClassToMetaSpace(String name, String fullName) throws IOException {
        // 通过class问价绝对地址来获取class文件
        File classFile = new File(fullName);
        // 创建数组来存放class文件
        byte[] classData = new byte[(int)classFile.length()];
        FileInputStream inputStream = new FileInputStream(classFile);
        // 读取数据到数组中
        inputStream.read(classData);
        inputStream.close();
        // 加载类到元空间 在方法区生成一个Class类的对象
        return defineClass(name, classData, 0, (int)classFile.length());
    }


    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        Class cls = null;
        // 查找已经加载的类
        cls = findLoadedClass(name);
        if (!classNameSet.contains(name) && cls == null){
            cls = getSystemClassLoader().loadClass(name);
        }
        if (cls == null){
            throw new ClassNotFoundException();
        }
        return cls;
    }
}

测试类:

package net.ziruo.classloader;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Timer;
import java.util.TimerTask;

/**
 * @Author: october
 * @Date: 2019/10/25 17:18
 * @Description:
 */
public class HotClassLoaderTest {

    public static void main(String[] args) {

        new Timer().schedule(new TimerTask() {
            @Override
            public void run() {
                final HotClassLoader hotClassLoader = new HotClassLoader("E:\\Code\\JAVA\\netty-demo\\target\\classes\\",
                        new String[]{"net.ziruo.classloader.Apple"});
                // 重新加载类
                Class<?> cls = null;
                try {
                    cls = hotClassLoader.loadClass("net.ziruo.classloader.Apple");
                    Object o = cls.newInstance();
                    Method method = cls.getMethod("say", null);
                    method.invoke(o, new Object[]{});
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                } catch (NoSuchMethodException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InstantiationException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                }
                System.out.println(cls.getClassLoader().toString());

            }
        }, 0, 3000L);
    }

}

实现效果:

参考文章:

www.ibm.com/developerwo… www.ibm.com/developerwo… www.blogjava.net/vincent/arc…