Skip to content

双亲委派机制深度解析

本文深入解释"双亲委派机制的局限性"

  • 灵活性不足 :现代框架(如Spring Boot、OSGi)有时需要打破双亲委派,实现热部署或模块隔离。
  • SPI机制冲突 :某些服务提供者接口(SPI,如JDBC驱动)需要父加载器加载子类,此时需通过线程上下文类加载器(Thread.currentThread().getContextClassLoader())绕过双亲委派。

Springboot热部署实现原理

热部署的核心目标是 在不重启 JVM 的情况下重新加载修改后的类 。双亲委派机制默认会优先委托父类加载器加载类,导致修改后的类无法被动态替换。Spring Boot 通过以下方式实现热部署:

  1. 自定义类加载器, Spring Boot 使用 LaunchedURLClassLoader(扩展自 URLClassLoader)作为应用类加载器, 绕过双亲委派 ,直接从类路径(Classpath)中加载用户类。当检测到代码变更时, 会销毁旧的类加载器和相关实例, 再创建新的类加载器, 加载修改后的类, 再通过反射调用新类的方法, 实现功能更新
  2. 依赖springboot-devtools工具包, 自动监测类路径变化触发重启, 或使用JRebel等商业工具等

模块化与类加载器隔离

双亲委派默认的层级加载会导致模块间类冲突(例如不同模块依赖同一类的不同版本)。为解决此问题,模块化框架(如 OSGi、J2EE)采用打破双亲委派的策略:每个模块使用独立的类加载器, 避免类版本冲突(如 log4j 的不同版本共存),支持模块动态加载/卸载(如插件化架构)

SPI机制

  • SPI(Service Provider Interface)机制要求核心接口由启动类加载器(BootstrapClassLoader)加载, 接口实现由应用类加载器(AppClassLoader)加载; 这样是为了确保不同应用使用同一份接口定义, 如JDBC 驱动加载场景:
    • 接口类(如 java.sql.Driver)由启动类加载器(BootstrapClassLoader)加载。
    • 实现类(如 com.mysql.cj.jdbc.Driver)由应用类加载器(AppClassLoader)加载。
  • java.sql.Driver类不在$JAVA_HOME/jre/lib路径下, 启用类加载器无法加载, 这里是由java提供的线程上下文类加载器, 允许临时切换类加载器, 来解决的
// 获取当前线程上下文类加载器(AppClassLoader)
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
// 使用上下文类加载器加载驱动类
Class<?> driverClass = contextClassLoader.loadClass("com.mysql.cj.jdbc.Driver");