Featured image of post Java 类加载器(二)

Java 类加载器(二)

自定义一个类加载器,实现不重启项目自动更新修改的内容

在上一篇文章中,我们介绍了类加载器的一些基本内容,在文章最后我们简单介绍了一下自定义类加载器的方式,但是没有展开进行细说,那么在本篇文章中,我们来手动实现一个类加载器,使得完成修改代码后,类加载器能够自动加载新的代码。

实现思想

依据上一篇文章最后的说明,我们知道一般实现自定义的类加载器,只需要重写findClass()方法即可,但是这种方法实现的类加载器依然遵循双亲委派模型,所以正常情况下针对类加载任务依然会委托给父类进行加载,这样的话可能并不适合自定加载最新代码要求,所以我们在这里使用重写loadClass()方法的方式来自定义一个类加载器。

自定义类加载器

基本原理

通过重写loadClass()方法,在该方法中添加针对特殊类的判断,即如果某个类位于某个package下,那么直接使用自定义的类加载器,而不在依托双亲委派模型让父类进行记载。同时,通过判断该目录下的文件修改日期是否与上一次修改时间相同来决定使用新的类加载器进行加载还是使用上一次的类加载器进行加载。

为何需要重新加载类加载器来加载新的类?

因为在java虚拟机中每一个类有且仅有一个,不可能存在两个相同的类,而判断两个类是否相同是依据类名、包名以及类加载器是否相同来判断的,所以当两个类的类名和包名都相同的情况下,必须使用不同的类加载器加载这个类,才能让这个类存在于内存中。因此针对一个已经修改过内容的类,如果想让它能够被加载到内存中,必须使用新的类加载器来加载。

代码实现

loadClass()方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
@Override
public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
    Class clazz = null;
    //查看HotSwapURLClassLoader 实例缓存下是否已经加载过class
    //不同的HotSwapURLClassLoader实例是不是共享缓存
    clazz = findLoadedClass(name);
    if (clazz != null) {
        if (resolve) {
            resolveClass(clazz);
        }
        //如果class类被修改过,则重新加载
        if (idModify(name)) {
            hcl = new HotSwapURLCLassLoader();
            clazz = customLoad(name, hcl);
        }
        return (clazz);
    }

    if (name.startsWith("org.yinan.exec")) {
        return customLoad(name, this);
    }
    try {
        ClassLoader system = ClassLoader.getSystemClassLoader();
        clazz = system.loadClass(name);
        if (clazz != null) {
            if (resolve) {
                resolveClass(clazz);
            }
        }
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    }
    return (clazz);
}

customLoad()方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
public Class customLoad(String name, boolean resolve, ClassLoader cl) throws ClassNotFoundException {
    //调用的是HotSwapURLCLassLoader里面重载了ClassLoader的findClass()方法
    Class clazz = ((HotSwapURLCLassLoader)cl).findClass(name);
    if (resolve) {
        ((HotSwapURLCLassLoader)cl).resolveClass(clazz);
    }
    //缓存加载class文件的最后修改时间
    long lastModifyTime = getClassLastModifyTime(name);
    cacheLastModifyTimeMap.put(name, lastModifyTime);
    return clazz;
}

findClass()方法

1
2
3
4
5
6
7
8
9
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
    byte[] classData = getClasszData(name);
    if (classData == null) {
        throw new ClassNotFoundException();
    } else {
        return defineClass(name, classData, 0, classData.length);
    }
}

getClasszData()方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
private byte[] getClasszData(String className) {
    //路径转换如将org.yinan.exec转换成org/yinan/exec
    String path = classNameToPath(className);
    try {
        InputStream inputStream = new FileInputStream(path);
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        int bufferSize = 4096;
        byte[] buffer = new byte[bufferSize];
        int byteNumRead = 0;
        while ((byteNumRead = inputStream.read(buffer)) != -1) {
            baos.write(buffer, 0, byteNumRead);
        }

        return baos.toByteArray();
    } catch (IOException e) {
        e.printStackTrace();
    }

    return null;
}

详细代码

Licensed under CC BY-NC-SA 4.0
comments powered by Disqus