高 困难
打破双亲委派#
一句话答案#
通过线程上下文类加载器(SPI 场景)、重写 loadClass()、OSGi/Tomcat 模块化来打破双亲委派。
核心要点
双亲委派模型回顾: 加载请求先委托给父加载器,直到 Bootstrap → 父加载器无法加载时,子加载器才自行加载。
为什么要打破?
- SPI 场景:核心类(rt.jar)需要加载第三方实现(如 JDBC Driver)
- 热部署:同一个类需要加载不同版本(OSGi/Tomcat)
打破方式:
-
线程上下文类加载器(TCCL):
- SPI 机制:
ServiceLoader用 TCCL 加载接口实现类 - 例如:
DriverManager(Bootstrap 加载)通过 TCCL 加载具体的 MySQL Driver
- SPI 机制:
-
重写 loadClass():
- 默认 loadClass 实现了双亲委派逻辑
- 自定义类加载器可重写此方法改变委派顺序
-
OSGi/模块化:
- 每个 Bundle 有自己的 ClassLoader
- 网状加载(非树状),实现模块隔离和热部署
Tomcat 的类加载:
- 每个 WebApp 有独立的 WebAppClassLoader
- 优先自己加载(WEB-INF/classes),找不到再委托父加载器
- 实现了不同应用加载不同版本的同名类
面试回答(2分钟版)
双亲委派模型要求类加载请求先委托给父加载器,父加载器无法加载时才由子加载器自行加载,保证了核心类库的安全性。但某些场景下需要打破这个模型,主要有三种方式。第一种是线程上下文类加载器,最典型的场景是 SPI 机制。比如 JDBC 的 DriverManager 由 Bootstrap 类加载器加载,但具体的 MySQL Driver 在应用的 classpath 下,Bootstrap 加载不了,于是通过 Thread.currentThread().getContextClassLoader() 获取应用类加载器来加载驱动实现类,实现了父加载器借助子加载器完成加载。第二种是重写 loadClass 方法,因为双亲委派逻辑就实现在 loadClass 中,重写它就能改变委派顺序。第三种是模块化方案,比如 Tomcat 为每个 WebApp 创建独立的 WebAppClassLoader,优先加载自己 WEB-INF/classes 下的类,找不到再委托父加载器,实现了不同应用加载不同版本同名类的隔离效果。区分 loadClass 和 findClass 很重要:loadClass 控制委派流程,findClass 控制实际加载逻辑,扩展用 findClass,打破用 loadClass。
追问与易错
追问方向:
- “JDBC 的 DriverManager 是怎么打破双亲委派加载驱动的?”→ TCCL 加载
- “SPI 的 ServiceLoader 原理?”→ 读取 META-INF/services 文件用 TCCL 加载实现类
- “Tomcat 中每个应用的类加载隔离怎么实现的?”→ 每个 WebApp 独立 ClassLoader
易错点:
- ❌ “重写 loadClass 就打破了”——也可以重写 findClass 在不改变委派逻辑的情况下扩展
- ❌ 混淆 loadClass 和 findClass——loadClass 实现委派逻辑,findClass 实现加载逻辑