面試官:JDBC 是如何打破雙親委派模型的?
在 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 仍然由擴展類加載器加載。