自定义类加载器

Java 默认 ClassLoader,只加载指定目录下的 class,如果需要动态加载类到内存,例如 要从远程网络下载的二进制文件类(*.class),然后调用这个类中的方法实现我的业务逻辑,如此,就需要自定义 ClassLoader。

自定义类加载器分为两步:

  1. 继承 java.lang.ClassLoader
  2. 重写父类的 findClass() 方法

针对第 1 步,为什么要继承 ClassLoader 这个抽象类,而不继承 AppClassLoader 呢? 因为它和 ExtClassLoader 都是 Launcher 的静态内部类,其访问权限是缺省的包访问权限。 static class AppClassLoader extends URLClassLoader{...}

第 2 步,JDK 的 loadCalss() 方法在所有父类加载器无法加载的时候,会调用本身的 findClass() 方法来进行类加载,因此我们只需重写 findClass() 方法找到类的二进制数据即可。

下面我自定义了一个简单的类加载器,并加载一个简单的类。

首先是需要被加载的简单类:

// 存放于D盘根目录
public class Test {

    public static void main(String[] args) {
        System.out.println("Test类已成功加载运行!");
        ClassLoader classLoader = Test.class.getClassLoader();
        System.out.println("加载我的classLoader:" + classLoader);
        System.out.println("classLoader.parent:" + classLoader.getParent());
    }
}

并使用 javac -encoding utf8 Test.java 编译成 Test.class 文件。

类加载器代码如下:

import java.io.*;

public class MyClassLoader extends ClassLoader {
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        // 加载D盘根目录下指定类名的class
        String clzDir = "D:\\" + File.separatorChar
                + name.replace('.', File.separatorChar) + ".class";
        byte[] classData = getClassData(clzDir);

        if (classData == null) {
            throw new ClassNotFoundException();
        } else {
            return defineClass(name, classData, 0, classData.length);
        }
    }

    private byte[] getClassData(String path) {
        try (InputStream ins = new FileInputStream(path);
             ByteArrayOutputStream baos = new ByteArrayOutputStream()
        ) {

            int bufferSize = 4096;
            byte[] buffer = new byte[bufferSize];
            int bytesNumRead = 0;
            while ((bytesNumRead = ins.read(buffer)) != -1) {
                baos.write(buffer, 0, bytesNumRead);
            }
            return baos.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
}

使用类加载器加载调用 Test 类:

public class MyClassLoaderTest {
    public static void main(String[] args) throws Exception {
        // 指定类加载器加载调用
        MyClassLoader classLoader = new MyClassLoader();
        classLoader.loadClass("Test").getMethod("test").invoke(null);
    }
}

输出信息:

Test.test()已成功加载运行!
加载我的classLoader:class MyClassLoader
classLoader.parent:class sun.misc.Launcher$AppClassLoader