在 Java 中,JDBC(Java Database Connectivity)是用于与数据库进行交互的标准 API。JDBC 并不是直接打破双亲委派模型,而是通过 SPI 机制加上线程上下文类加载器(Thread.currentThread().getContextClassLoader())的方式,绕过了传统的类加载委托机制。这种设计既保留了双亲委派模型的核心优势,又实现了对第三方 JDBC 驱动程序的动态加载。
1. JDBC 打破双亲委派模型的缘由
在 Java 中,DriverManager 是 JDBC 的核心类,用于管理 JDBC 驱动程序并建立数据库连接。但是DriverManager 所在的类加载器(扩展类加载器或启动类加载器)通常无法直接加载第三方 JDBC 驱动程序(例如 MySQL 的 com.mysql.cj.jdbc.Driver),因为这些驱动程序是由应用程序类加载器加载的。
为了能够加载第三方 JDBC 驱动程序,JDBC 会使用线程上下文类加载器(Thread.currentThread().getContextClassLoader())来加载驱动程序。线程上下文类加载器是一个特殊机制,它允许线程在其上下文中切换类加载器。通过这种方式,JDBC 可以绕过传统的双亲委派模型,使用特定的类加载器(通常是应用程序类加载器)来加载 JDBC 驱动程序。
2. 线程上下文类加载器+SPI 机制
- Java 提供了 SPI 机制来发现和加载服务实现。对于 JDBC,各个数据库厂商实现 java.sql.Driver 接口,并将实现类的全限定名配置在 META - INF/services/java.sql.Driver 文件中。下面是MySQL驱动中的SPI配置和Oracle驱动中的SPI配置
image.png
image.png
- 当应用程序需要使用 JDBC 驱动时,核心类库中的 DriverManager 类负责加载驱动。DriverManager 使用线程上下文类加载器( Thread.currentThread().getContextClassLoader() )来加载 JDBC 驱动实现类。
- 线程上下文类加载器通常是应用程序类加载器( AppClassLoader ),它可以加载应用程序的类路径下的类,包括各个数据库厂商提供的 JDBC 驱动实现类。这就打破了双亲委派模型中类加载器自下而上委派的规则,因为它不是从启动类加载器开始查找驱动类,而是从应用程序类加载器开始查找,使得数据库厂商的 JDBC 驱动类能够被正确加载。
通过这种方式,JDBC 打破了双亲委派模型,允许第三方 JDBC 驱动程序由应用程序类加载器加载,而 DriverManager 仍然由扩展类加载器加载。