Skip to content
困难

一句话答案

通过线程上下文类加载器(SPI 场景)、重写 loadClass()、OSGi/Tomcat 模块化来打破双亲委派。

核心要点

双亲委派模型回顾: 加载请求先委托给父加载器,直到 Bootstrap → 父加载器无法加载时,子加载器才自行加载。

为什么要打破?

  • SPI 场景:核心类(rt.jar)需要加载第三方实现(如 JDBC Driver)
  • 热部署:同一个类需要加载不同版本(OSGi/Tomcat)

打破方式:

  1. 线程上下文类加载器(TCCL)

    • SPI 机制:ServiceLoader 用 TCCL 加载接口实现类
    • 例如:DriverManager(Bootstrap 加载)通过 TCCL 加载具体的 MySQL Driver
  2. 重写 loadClass()

    • 默认 loadClass 实现了双亲委派逻辑
    • 自定义类加载器可重写此方法改变委派顺序
  3. 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。