双亲委派模型是classloader.loadclass()默认实现的委派策略:子加载器先委托父加载器加载,仅当所有上级失败才自行加载,核心价值在于保障核心类不被篡改、避免重复加载、维持层级一致性;其过程为自下而上委派(app→ext→bootstrap),再自上而下回退加载;因web隔离与spi等现实需求,需重写loadclass()打破委派,如逆向加载、使用上下文类加载器或按包名分治委派,但须有边界——如tomcat对java.*仍严格委派。

双亲委派模型不是硬性规范,而是 ClassLoader.loadClass() 方法默认实现的一种加载策略:子加载器收到类加载请求时,先向上委托父加载器尝试加载;只有所有上级都失败,才由自己加载。它的核心价值在于保障核心类(如 java.lang.String)不被替换、避免同一类被重复加载、维持类加载层级一致性。
双亲委派怎么工作的
整个过程分两步走:
- 向上委派:从应用类加载器(AppClassLoader)开始,依次委托给扩展类加载器(ExtClassLoader)、启动类加载器(Bootstrap ClassLoader)
- 向下加载:启动类加载器在
rt.jar中找;找不到就返回失败,请求逐级下传;最终落到发起者自己负责的路径(比如classpath或WEB-INF/lib)中查找并加载
为什么要打破它
现实场景中,双亲委派的“单向向上委托”逻辑会卡住关键需求:
-
Web 应用隔离需要:Tomcat 同时部署 Spring 5.3 和 Spring 6.0 的两个应用,若共用 AppClassLoader,后者会复用前者已加载的老版本类,导致
NoSuchMethodError -
SPI 机制需要反向可见:JDK 的
DriverManager(由 Bootstrap 加载)要加载 MySQL 的com.mysql.cj.jdbc.Driver(由应用类加载器加载),但启动类加载器无法向下委托,必须绕过委派直接让子加载器加载实现类
怎么打破双亲委派
关键在于重写 loadClass() 方法,改变默认的“先父后己”顺序。常见方式有三种:
- 在自定义类加载器中,把
findClass()调用提前到父加载器调用之前——先自己加载,失败再委派(即“逆向委派”) - 在特定时机(如 JDBC
ServiceLoader初始化时)显式使用线程上下文类加载器(Thread.currentThread().getContextClassLoader()),让父加载器间接调用子加载器 - 像 Tomcat 那样为每个 WebApp 创建独立的
WebAppClassLoader,并重写loadClass(),对javax.*、java.*等包仍走委派,其余包优先自己加载
打破不等于乱来
真正工程化的打破,是有边界的妥协:
- Tomcat 不加载
java.*和javax.servlet.*,这些仍由上层加载器负责,确保容器基础稳定 - JDBC 驱动注册只在
DriverManager内部触发一次,且依赖上下文类加载器,不破坏全局类加载结构 - 所有自定义加载器仍继承
ClassLoader,只是覆盖了关键方法,保留了链接、初始化等后续阶段的标准流程
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











