打破双亲委派场景
面试官问:"双亲委派能被打破吗?"
候选人小赵说:"不能吧,这是 JVM 的机制。"面试官追问:"那 Tomcat 怎么实现每个 Web 应用加载自己版本的 Spring 的?"
小赵愣在原地。
这道题的答案是:能打破,而且必须打破。双亲委派是一个优秀的设计原则,但不是铁律。当你有多个独立的模块需要各自的类隔离时,就必须打破它。
一、为什么要打破双亲委派 🔴
1.1 问题场景
场景一:多版本共存
场景二:热部署
场景三:SPI 机制
1.2 打破方式一览
二、场景一:Tomcat 的热部署与类隔离 🟡
2.1 Tomcat 类加载器的层级
2.2 WebAppClassLoader 的打破策略
Tomcat 的 WebappClassLoader 重写了 loadClass 方法,采用了相反的加载顺序:
关键点:Tomcat 的 WebAppClassLoader 先自己加载,再委托父加载器。这与标准的双亲委派完全相反。
2.3 为什么 Tomcat 要打破双亲委派?
2.4 Tomcat 的类加载器白名单
面试陷阱:有人说"Tomcat 完全抛弃了双亲委派"。准确答案是:不是完全抛弃,而是有选择地打破。Tomcat 的 WebAppClassLoader 仍然对 java.*、javax.* 等核心包使用标准双亲委派,因为这些类必须由 Bootstrap 加载。打破的只是应用类。
三、场景二:SPI 与线程上下文类加载器 🟡
3.1 JDBC 驱动的加载流程
JDBC 是打破双亲委派的经典案例,但很多人不清楚它的完整流程。
3.2 DriverManager 的加载逻辑
完整流程:
3.3 JNDI 的场景
JNDI 的 InitialContext 也是类似:
3.4 面试追问:如何破坏双亲委派?
直接方式:重写 ClassLoader.loadClass()
但这种写法是反模式。正确的方式是理解业务场景后,有选择地打破。
四、场景三:OSGi 的模块化类加载 🟢
4.1 OSGi 的设计思想
OSGi(Open Service Gateway Initiative)是一个模块化框架,每个 Bundle 有自己的 ClassLoader:
4.2 OSGi 的类加载策略
OSGi 使用导入/导出包的方式管理类加载:
OSGi 的类加载器不再遵循双亲委派,而是遵循模块依赖图:
4.3 OSGi vs Tomcat
面试加分点:能说出"OSGi 的类加载器之间通过 Bundle 的依赖关系来确定加载顺序,而不是简单的父子层级。当 Bundle A 依赖 Bundle B 时,加载顺序是 B → A。这是 OSGi 能够支持真正的模块化和动态更新的核心",说明他对模块化有深入理解。
五、生产踩坑:类加载器冲突排查 🟡
5.1 典型冲突现象
5.2 排查方法
【架构权衡】 打破双亲委派是为了解决真实问题:
- Tomcat 打破是为了 Web 应用隔离
- SPI 打破是为了跨模块依赖注入
- OSGi 打破是为了真正的模块化
但打破意味着放弃安全的保护,所以必须慎之又慎。Tomcat 用白名单限制必须委派的包,SPI 用线程上下文类加载器这个"线程级"的上下文传递——每种打破方式都有自己的安全边界。