自定义类加载器以appclassloader为父加载器,遵循双亲委派:先委托父类加载,失败后重写findclass从非标准路径读取字节码并调用defineclass注册;需注意类隔离、反射调用一致性及避免静态变量跨加载器共享。

应用程序类加载器(AppClassLoader)本身不直接加载自定义类,它只负责加载 classpath 路径下的类(如项目 class 目录、lib 下的 JAR)。如果你写的类在标准 classpath 中,它自然会被 AppClassLoader 加载;但若想加载非标准路径(比如磁盘任意目录、网络、加密文件等)中的类,就必须绕过它——用自定义类加载器。
自定义类加载器如何与 AppClassLoader 协同工作
自定义类加载器通常以 AppClassLoader 为父加载器,遵循双亲委派模型:先委托父类尝试加载,失败后再自己动手。这样既能复用已有机制,又能接管特定类的加载逻辑。
- 构造时显式传入父加载器:
super(ClassLoader.getSystemClassLoader()) - 调用
loadClass("com.example.MyClass")时,会先由 AppClassLoader 查找 classpath,查不到才触发你的findClass - 确保你加载的类名和路径匹配(如
com.example.MyClass对应/myclasses/com/example/MyClass.class)
关键步骤:重写 findClass 而不是 loadClass
AppClassLoader 的 loadClass 方法已实现双亲委派逻辑,你只需专注“从哪读字节码”和“怎么转成 Class”。核心是重写 findClass(String name):
- 把类名转为路径(
name.replace('.', '/') + ".class") - 按路径读取字节数组(支持文件、URL、数据库、内存解密等)
- 调用
defineClass(name, bytes, 0, bytes.length)注册为 JVM 可识别的 Class 对象
加载后要注意类隔离和反射调用
同一个类名,被不同类加载器加载,JVM 视为两个完全无关的类。这意味着:
-
instanceof或强制转型可能失败(即使类型名相同) - 若通过反射调用方法,需确保目标类和参数类都由同一自定义加载器加载
- 避免静态变量跨加载器共享——它们在各自加载器的命名空间中独立存在
一个典型使用场景示例
你想热更新 PluginService.class,把它放在 /plugins/ 目录下:
- 编写
PluginClassLoader extends ClassLoader,构造时指定/plugins路径 - 每次更新后新建一个该加载器实例(避免旧类残留),加载新字节码
- 用
newInstance()或反射创建实例,再通过接口(如Service)统一调用,规避类类型冲突











