Java自定义类加载器需继承ClassLoader并重写findClass而非loadClass,通过defineClass将字节码转为Class对象,支持本地、JAR、内存、网络等多源加载,配合独立实例与合理parent设置实现类隔离。
Java 中自定义类加载器实现类的动态加载,核心在于绕过默认的双亲委派机制,自主控制字节码获取与类定义过程。关键不是“替换”系统加载器,而是构造一个独立的、可按需触发的加载路径。
继承 ClassLoader 并重写 findClass
自定义类加载器必须继承 java.lang.ClassLoader,且**不应重写 loadClass 方法**(以免破坏双亲委派的安全基础),而应覆盖 findClass(String name)。这个方法只在父类加载器无法加载时被调用,是插入自定义逻辑的安全入口。
- 在 findClass 中,根据类名(如 "com.example.Service")拼出对应 .class 文件路径或远程 URL
- 读取字节码:本地文件用 FileInputStream,网络资源用 URLConnection,JAR 包用 JarFile + ZipEntry
- 将原始字节数组传给 defineClass(name, bytes, 0, bytes.length),生成 Class 对象
支持多种字节码来源
动态加载的价值在于来源灵活。一个实用的自定义加载器通常能处理:
-
本地目录:指定 rootPath,按包名转路径(如 com.example.Foo → /root/com/example/Foo.class)
-
JAR/WAR 文件:用 JarFile 扫描 entries,匹配类名后提取 InputStream
-
内存字节码:比如编译器(javax.tools.JavaCompiler)动态生成的 .class 字节数组,直接缓存并返回
-
网络资源:通过 HTTP 下载 class 或 JAR,注意校验和与超时控制
避免类冲突与隔离
动态加载常用于插件或热更新场景,此时需防止新旧类共存引发 LinkageError。关键做法包括:
立即学习“Java免费学习笔记(深入)”;
- 每个插件或模块使用独立的类加载器实例,彼此不共享命名空间
- 显式设置 parent 加载器(如传入 Thread.currentThread().getContextClassLoader()),而非默认 null(即 Bootstrap)
- 若需访问宿主类,确保 parent 链路可达;若需完全隔离,可设 parent 为 null(需自行加载 java.* 等基础类,慎用)
加载后如何使用
得到 Class 对象只是第一步,还需实例化和调用:
- 用 Class.getDeclaredConstructor().newInstance() 创建对象(注意无参构造)
- 通过反射调用方法:clazz.getMethod("doWork").invoke(instance)
- 更规范的做法是定义统一接口(如 Plugin 接口),让动态类实现它,再强转调用,避免硬编码方法名
不复杂但容易忽略:每次加载前应检查是否已加载(findLoadedClass),避免重复 defineClass 导致 ClassCastException;同时注意类卸载依赖加载器对象不可达,实际中需配合弱引用与显式清理。