Java类加载器本身不提供动态扫描能力,真正的动态扫描与加载需结合类路径遍历、字节码解析和自定义ClassLoader实现:先扫描定位.class文件或JAR资源,再通过重写findClass方法加载字节码,配合独立ClassLoader实现隔离,并注意引用清理以支持GC回收。

Java 类加载器本身不直接提供“动态扫描”能力,它只负责将字节码加载进 JVM 并生成 Class 对象。真正的“动态扫描与加载”是结合类路径(classpath)、文件系统或 JAR 包遍历、字节码解析和自定义类加载器共同完成的。
扫描:定位潜在的类文件
扫描不是类加载器的工作,而是由应用逻辑完成。常见做法是:
- 遍历指定目录(如
classes/或lib/)下的.class文件,或解压 JAR/WAR 中的类路径条目 - 利用
ClassLoader.getResources("package/path/")获取资源路径(注意末尾斜杠),再解析返回的URL列表,区分 file://、jar:// 等协议并分别处理 - 使用工具类如
org.springframework.core.io.support.PathMatchingResourcePatternResolver或Reflections库简化扫描逻辑
加载:用自定义 ClassLoader 加载字节码
标准类加载器(如 AppClassLoader)默认不支持重复加载或从任意路径加载。要实现动态加载,需继承 ClassLoader 并重写 findClass(String name):
- 在
findClass中根据类名转换为内部路径(如com.example.Foo→com/example/Foo.class) - 读取对应字节码(从文件、网络、数据库等来源),调用
defineClass(byte[], off, len)得到Class实例 - 避免覆盖
loadClass,保留双亲委派机制;仅在父加载器找不到时才调用findClass
隔离与卸载:避免类冲突和内存泄漏
动态加载常用于插件、热部署等场景,需注意:
立即学习“Java免费学习笔记(深入)”;
- 每个插件使用独立的
ClassLoader实例,实现类空间隔离(不同加载器加载同名类互不可见) - JVM 不支持真正意义上的“卸载类”,但可让类加载器及其加载的所有类被 GC 回收——前提是所有对该
Class或其实例的引用都被清除(包括线程、静态字段、缓存等) - 避免在自定义加载器中持有对应用类的强引用(如通过
Thread.currentThread().getContextClassLoader()间接引用),否则会阻止卸载
典型流程示例
以扫描并加载某个包下所有实现 Plugin 接口的类为例:
- 扫描
classpath:/com/example/plugin/下所有.class文件 - 过滤出非接口、非抽象类,且继承/实现
Plugin的类名 - 用自定义
PluginClassLoader加载该类,并通过Class.newInstance()或反射构造器创建实例 - 调用插件方法前,确保其依赖类也已加载(可通过设置父加载器或共享依赖类加载器解决)
不复杂但容易忽略细节,关键在于扫描逻辑的健壮性、类加载器生命周期管理,以及打破双亲委派时的边界控制。


















