java中如何自定义一个类加载器?

59 阅读3分钟

我们除了在面试中遇到类的加载器的概率高一点外,在平时的工作中其实是比较少接触的,你平时工作中很少有说需要你从外部环境去获取加载class字节码文件的,但是在学习自定义类加载器的过程中,我们会对JVM的类加载机制和类加载过程更加了解,所以我们还是有必要来学习一下的。

在java中,类加载器(ClassLoader)是负责将类文件加载到JVM中的组件。实现自定义类加载器可以让你控制类加载的过程,比如可以从网络、数据库、从你指定的磁盘位置等地方加载类。

一、自定义类加载器的步骤

  1. 继承ClassLoader类:自定义的类加载器需要继承java.lang.ClassLoader类。

  2. 重写findClass方法:重写findClass(String name)方法,这个方法用于定义类的加载逻辑。

  3. 调用defineClass方法:在findClass方法中,通过defineClass方法将字节数组转换为Class对象。

从文件系统加载类,代码示例如下:

先重写findClass方法:

public class MyClassLoader extends ClassLoader{
    private String classPath;
    public MyClassLoader(String classPath){
        this.classPath = classPath;
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        try {
            // 将类名转换成文件路径(我这个例子中是 D:/pojo/Test.class)
            String fileName = classPath + name.replaceAll("\\.", "/") + ".class";
            //读取类文件中的字节码
            byte[] classBytes = Files.readAllBytes(Paths.get(fileName));
            return defineClass(name, classBytes, 0, classBytes.length);
        }catch (Exception e){
            throw new ClassNotFoundException(name);
        }
    }

}

然后我准备了一个Test类

image-20250923104010236.png

然后我们要将这个Test类用javac命令进行编译:

image-20250923104154823.png

image-20250923104232530.png

image-20250923104242163.png

我们将这个Test.class字节码文件拷贝到D盘个目录里面去:

image-20250923121717134.png

测试类加载器:

public class TestDemo {
    public static void main(String[] args) {
        try {
            //创建自定义类加载器,指定类文件所在路径(我们在上一步已经将Test.class字节码文件拷贝到D盘根目录中去)
            MyClassLoader myClassLoader = new MyClassLoader("D:/");
            //加载类
            Class<?> clazz = myClassLoader.loadClass("pojo.Test");//参数为类的全限定类名
            //得到class字节码后,使用反射来创建对象
            Object instance = clazz.getDeclaredConstructor().newInstance();
            //打印出这个对象所属的类名
            System.out.println(instance.getClass().getName());
            //打印出instance这个对象所属的类 用的类加载器的名称
            System.out.println("该类由:"+instance.getClass().getClassLoader().getClass().getName()+" 进行加载");
            //调用instance实例中的方法
            clazz.getMethod("eat").invoke(instance);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

结果:

image-20250923121610960.png

由结果可以看到,自己写的类加载器,已经把我电脑D盘下的 Test.class字节码文件,加载进JVM了,并且使用自定义类加载器加载进来的字节码反射生成了一个instance对象。

总结

自定义类加载器的核心在于对字节码文件的获取。这里我们需要注意几个点:

  1. 最好不要重写loadClass方法,因为这样容易破坏双亲委托模式。
  2. 这里Test 类本身可以被AppClassLoader类加载,因此我们不能把Test.class放在类路径下。否则,由于双亲委托机制的存在,会直接导致该类由AppClassLoader加载,而不会通过我们自定义类加载器来加载。