📜  自定义类加载器 (1)

📅  最后修改于: 2023-12-03 15:41:24.369000             🧑  作者: Mango

自定义类加载器

Java中,类加载器是将类字节码数据从不同来源转换成可执行代码的核心组件。系统默认提供的类加载器有三种,分别是:

  • Bootstrap ClassLoader:负责加载JVM核心类库,用C++语言实现,所以无法在Java中直接使用。
  • Extension ClassLoader:扩展类加载器,加载Java平台类库中‘$JAVA_HOME/lib/ext’目录下的jar包。
  • AppClassLoader:也叫系统类加载器,负责加载应用程序classpath目录下的类库。

如果系统自带的类加载器不能满足我们的需求,就需要自定义类加载器来实现自己的需求。

为什么要使用自定义类加载器

Java虚拟机自带的类加载器都有固定的加载策略,无法满足特定的需求,比如:

  • 加密或压缩后的类文件
  • 动态生成的类
  • 非标准形式存储的类文件

这些特殊的类文件都需要自定义类加载器才能够被加载到内存中。另外,如果有多个版本的Jar包,想让JVM同时加载两个版本的Jar包,也需要自定义类加载器来实现。

实现自定义类加载器

自定义类加载器需要实现ClassLoader抽象类,一般的实现方式有两种:

  • 继承URLClassLoader类:URLClassLoader是ClassLoader的直接子类,主要作用是从指定的URL载入类和资源,可通过调用addURL()方法添加Jar包或目录。
  • 继承ClassLoader类并覆盖findClass()方法:findClass()方法是ClassLoader中用于查找类的方法。

实现代码:

public class MyClassLoader extends ClassLoader {

    private String baseDir;// 加载类的基础路径

    public MyClassLoader(String baseDir) {
        this.baseDir = baseDir;
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        byte[] data = loadClassData(name);// 读取class文件的字节码数据
        return defineClass(name, data, 0, data.length);
    }

    private byte[] loadClassData(String name) throws ClassNotFoundException {
        name = baseDir + File.separatorChar + name.replace('.', File.separatorChar) + ".class";
        try (InputStream in = new FileInputStream(name)) {
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            byte[] buffer = new byte[1024];
            int length = -1;
            while ((length = in.read(buffer)) != -1) {
                out.write(buffer, 0, length);
            }
            return out.toByteArray();
        } catch (IOException e) {
            throw new ClassNotFoundException("Class " + name + " not found", e);
        }
    }
}
使用自定义类加载器

使用自定义类加载器需要注意以下问题:

  • 需要调用defineClass()方法将类的字节数组转换成Class对象。
  • 如果需要重载已有的类,需要使用同一个类加载器实例。
  • 自定义类加载器需要使用双亲委派模型。

示例代码:

MyClassLoader classLoader = new MyClassLoader("path/to/classes");
Class<?> clazz = classLoader.loadClass("com.demo.MyClass");
Object obj = clazz.newInstance();
双亲委派模型

在ClassLoader的加载过程中,会交由父类加载器来先尝试加载指定的类,只有在父类加载器无法加载时,才会交由自定义类加载器来加载。

双亲委派模型的优点是防止类的冲突和避免类的重复加载,缺点是无法加载自定义的类和第三方类。

为了避免自定义类被父类加载器加载,需要在自定义类加载器中覆盖loadClass()方法,禁止使用双亲委派模型。

protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
    Class<?> clazz = findLoadedClass(name);
    if (clazz == null) {
        if (name.startsWith("com.demo")) {
            clazz = findClass(name);
        } else {
            clazz = super.loadClass(name, resolve);
        }
    }
    if (resolve) {
        resolveClass(clazz);
    }
    return clazz;
}
总结

自定义类加载器可以解决部分特殊场景的需求,但是使用时也需要注意双亲委派模型的问题。除非有确切的需求否则不建议使用,避免引入不必要的麻烦。