getResource 遵循双亲委派,先委托父加载器查找,失败后才调用自身 findResource;findResource 是子类实现资源定位的钩子,默认返回 null,必须重写才能支持自定义路径加载。

自定义类加载器中,getResource 和 findResource 行为差异的关键,在于是否遵循双亲委派、是否触发父加载器查找、以及谁真正负责定位资源路径。
getResource 的委派顺序:先父后己,只取第一个匹配
调用 getResource(name) 时,类加载器不会自己立刻查找,而是严格按双亲委派流程执行:
- 如果当前加载器有父加载器,先调用
parent.getResource(name) - 若父返回非 null URL,直接返回,不再往下走
- 若父返回 null(包括父为 null 时调用
getBootstrapResource也失败),才调用自身的findResource(name) - 最终只返回第一个成功找到的 URL,不管后续加载器是否还有同名资源
findResource 是实际查找入口,但默认不干活
findResource(String name) 是 protected 方法,ClassLoader 基类中直接返回 null,它本身不做任何路径扫描——它的作用是留给子类实现具体查找逻辑的钩子:
- URLClassLoader 的子类(如 SpringBoot 的 LaunchedURLClassLoader)会重写它,遍历内部维护的 URL 列表(如 jar:file:/xxx.jar!/ 或 file:/classes/)逐个检查资源是否存在
- 自定义类加载器若想支持资源加载,必须重写
findResource,否则getResource在父加载器失败后也会返回 null - 注意:
findResource只负责找单个 URL;若需找所有匹配资源,应使用getResources(name)(它会递归调用各层的findResources)
常见陷阱:getResourceAsStream 为什么有时为空
很多问题表面是流为空,根源其实是 getResource 已经失败。典型场景包括:
- 路径写成
"config/app.properties"(相对路径),但当前类加载器的类路径根不含该目录,而父加载器(如 AppClassLoader)也没配对 —— 最终findResource返回 null - 在 Web 应用中误用
MyClass.class.getClassLoader().getResourceAsStream(...),而实际资源在 WEB-INF/classes 下,应优先用上下文类加载器:Thread.currentThread().getContextClassLoader().getResourceAsStream(...) - 自定义加载器未重写
findResource,却期望它能从自定义位置(如数据库、远程 HTTP)加载资源
正确实现自定义资源加载的最小步骤
若你继承 ClassLoader 并希望支持资源加载,至少需完成以下两件事:
- 在构造时明确指定父加载器(避免意外退化为 Bootstrap 加载器),例如:
super(parent != null ? parent : ClassLoader.getSystemClassLoader()) - 重写
findResource(String name),按你的资源来源逻辑返回 URL(如从本地目录扫描、解压 ZIP、读取 classpath 下特定前缀的 JAR 等) - 可选但推荐:同时重写
findResources(String name),以支持getResources多结果语义

















