双亲委派机制简单来说就是类加载的一个顺序机制,简单来说就是:当一个类加载器收到加载请求时,检查该类是否已经被当前加载器加载过。如果已加载,直接返回;若未加载,开始委派,先委托父加载器去加载,父加载器加载不了,才自己加载。

首先介绍一下类加载器有哪几个?
1、启动类加载器:负责加载 <JAVA_HOME>/lib 目录下的核心类库(如 rt.jar、resources.jar、sun.boot.class.path 路径下的包
2、扩展类加载器:负责加载 <JAVA_HOME>/lib/ext 目录,或者由 -Djava.ext.dirs 系统变量指定路径下的所有类库。
3、应用程序类加载器:负责加载用户类路径(ClassPath)、-classpath 或 -cp 参数指定的类库
那么为什么需要双亲委派机制呢?
1、为了保证核心类库安全:如果用户自己创建一个Java.lang.String的类放在自己的项目下,那么根据双亲委派机制的加载逻辑,只有最高层级的启动类加载器才能进行加载,这样就保证了核心类的安全性
**2、 避免类重复加载:**从最底层的应用程序类加载器开始检查一直到最上层的类加载器,只要检查到这个类已经被加载了,那么就会直接返回确保了类只加载一次
3、保证类的唯一性: 类身份 = (全限定名, 定义类加载器),同一个类名,最终由同一个定义类加载器加载,得到同一个 Class 对象
那么有些场景为什么要打破双亲委派机制?如何打破双亲委派机制?
1、Tomcat启动多个应用
我们知道Tomcat可以启动多个应用,每个应用可能会出现多个spring依赖的版本,那么不同的spring版本依赖的名称肯定是相同的,那么根据双亲委派机制,就会把相同的名称的依赖包下的类只加载一次,这样就会出现版本不一致的情况
那么如何解决呢?
修改ClassLoader 并重写 loadClass 方法
1、设计每个应用都有独立的应用程序类加载器
2、BootstrapClassLoader 尝试去加载(仅针对 java.* 等核心包做安全隔离)。
WebAppClassLoader 优先尝试去加载(直接去 WEB-INF/classes 和 WEB-INF/lib 找)。
System/CommonClassLoader 最后才尝试去加载(如果本地 WEB-INF 找不
2、JDBC
DriverManager 位于 java.sql 包,由启动类加载器 Bootstrap ClassLoader 加载。DriverManager 需要加载 MySQL 的 com.mysql.cj.jdbc.Driver,这个类在 CLASSPATH 下,Bootstrap 加载不了。
那么如何解决呢?
SPI 与线程上下文类加载器
核心库的加载器无法向下识别第三方驱动,于是引入 Thread.currentThread().getContextClassLoader(),让父加载器逆向请求子加载器去加载驱动实现。上下文类加载器就是应用程序类加载器,这样就能够将驱动类正常加载
总结:
想要打破双亲委派机制有两种方式:
1、自定义ClassLoader 并重写 loadClass 方法
2、改变加载顺序,如:调用上下文类加载器
最后附上一个表格关于是否重写对应的类加载器的方法
| 方法 | 职责 | 是否可重写 |
|---|---|---|
loadClass |
双亲委派逻辑 | 可重写,但不推荐 |
findClass |
读取字节流,定义类 | 推荐重写 |
defineClass |
把字节流变成 Class 对象 | final,不可重写 |
findLoadedClass |
检查是否已加载 | final |
resolveClass |
触发链接 | final |