class.forname 默认使用当前类的类加载器而非线程上下文类加载器,排查需确认加载器能否访问目标类、检查tccl是否被污染、验证模块导出与依赖关系。

排查因 Class.forName 使用错误类加载器引发的 ClassNotFoundException,关键不是“类不存在”,而是“当前类加载器看不到它”。这类问题在 Web 容器、插件系统、模块化环境或自定义类加载场景中尤为常见。
确认 Class.forName 默认使用哪个类加载器
默认调用 Class.forName("xxx.Yyy") 等价于:Class.forName("xxx.Yyy", true, this.getClass().getClassLoader())
即:使用当前类所在类加载器,而非线程上下文类加载器(TCCL)或系统类加载器。
- 若你的类在
lib/下,但当前类由WebAppClassLoader加载(如 Tomcat 的 WEB-INF/classes),而目标类在shared/lib,则默认加载器无法委托到父加载器——除非显式配置了委托策略 - 在 Spring Boot 的 fat jar 中,
this.getClass().getClassLoader()通常是LaunchedURLClassLoader,它能访问所有打包进 jar 的依赖;但若你手动切换了 TCCL,就可能绕过它
检查线程上下文类加载器是否被意外覆盖
很多框架(如 JNDI、JAX-WS、某些 RPC 工具)会在执行前设置 Thread.currentThread().setContextClassLoader(...)。一旦被设为一个受限加载器(例如只加载特定包),后续 Class.forName 就会失败。
- 调试时可打印:
System.out.println(Thread.currentThread().getContextClassLoader()); - 若发现 TCCL 是
NullClassLoader、RestrictedClassLoader或明显不包含目标 jar 的 URLClassLoader,说明它被上游污染 - 临时修复:在调用前重置为当前类的加载器:
Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
验证目标类是否真能被指定加载器访问
不要只依赖异常信息,要主动测试加载器能力:
- 用目标加载器直接尝试加载:
loader.loadClass("com.example.TargetClass")—— 若抛异常,说明该 loader 确实无权访问 - 检查 loader 的资源路径:
loader.getResources("com/example/TargetClass.class"),看是否返回非空枚举 - 对
URLClassLoader,可遍历其getURLs(),确认对应 jar 或目录是否存在且可读
模块化环境(Java 9+)下需额外校验模块可见性
即使类加载器正确,模块系统仍会拦截访问:
- 目标模块必须
exports包(如exports com.example.api;) - 当前模块必须
requires目标模块(如requires com.example.lib;) - 若用
Class.forName(name, true, loader)指定了 loader,该 loader 必须来自已解析的ModuleLayer,否则模块约束不生效










