外观
一句话答案
通过线程上下文类加载器(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),找不到再委托父加载器
- 实现了不同应用加载不同版本的同名类
追问与易错
追问方向:
- JDBC 的 DriverManager 是怎么打破双亲委派加载驱动的?(TCCL 加载)
- SPI 的 ServiceLoader 原理?(读取 META-INF/services 文件用 TCCL 加载实现类)
- Tomcat 中每个应用的类加载隔离怎么实现的?(每个 WebApp 独立 ClassLoader)
易错点:
- ❌ "重写 loadClass 就打破了"——也可以重写 findClass 在不改变委派逻辑的情况下扩展
- ❌ 混淆 loadClass 和 findClass——loadClass 实现委派逻辑,findClass 实现加载逻辑
💡 记忆锚点
三种打破姿势:SPI用线程上下文类加载器让Bootstrap"借"Application的手加载驱动(JDBC典型),重写loadClass绕开委派逻辑,Tomcat每个WebApp独立ClassLoader实现同名类多版本共存。loadClass管委派流程,findClass管实际加载——扩展用findClass,打破用loadClass。