引言
Java類加載機制中的雙親委派模型通過層層委托保證了核心類加載器與應用類加載器之間的職責分離和加載安全性,但其單向的委托關系也帶來了一些局限性。尤其是在核心類庫需要訪問或實例化由應用類加載器加載的類時,雙親委派模型無法滿足需求,導致系統類無法直接調用應用類中的實現。為解決這一問題,Java采用了線程上下文類加載器(Context ClassLoader)機制,打破了傳統雙親委派的限制,使核心類庫能夠通過當前線程的上下文類加載器訪問應用層的擴展類。典型應用如JDBC中的DriverManager通過服務提供者接口(SPI)機制,利用上下文類加載器動態加載數據庫驅動,實現了靈活可擴展的設計。本文將圍繞雙親委派模型的不足及其被打破的原因,深入探討SPI機制及線程上下文類加載器的作用與實現原理。
1. 什么是雙親委派模型?
https://zhuanlan.zhihu.com/p/612318527https://zhuanlan.zhihu.com/p/612318527
2. 為什么要打破雙親委派模型?
檢查類是否加載的委托過程是單向的,這個方式雖然從結構上說比較清晰,使各個ClassLoader的職責非常明確,但是同時會帶來一個問題,即頂層的ClassLoader無法訪問底層的ClassLoader所加載的類。
通常情況下,啟動類加載器中的類為系統核心類,包括一些重要的系統接口,而在應用類加載器中,為應用類。按照這種模式,應用類訪問系統類自然是沒有問題,但是系統類訪問應用類就會出現問題。比如在系統類中提供了一個接口,該接口需要在應用類中得以實現,該接口還綁定一個工廠方法,用于創建該接口的實例,而接口和工廠方法都在啟動類加載器中。這時,就會出現該工廠方法無法創建由應用類加載器加載的應用實例的問題。
3. SPI是如何打破雙親委派機制的?
由于雙親委派模型的限制,父加載器無法訪問由子加載器加載的類,所以核心類庫無法直接訪問這些SPI實現類。當核心類庫需要調用SPI實現時,它會通過Thread.currentThread().getContextClassLoader()獲取當前線程的上下文類加載器,從而可以訪問到應用層的實現類。
DriverManager是JDBC里管理和注冊不同數據庫Driver的工具類。在使用Java JDBC進行數據庫編程時,我們引入驅動包之后,直接使用Connection connection = DriverManager.getConnection(url,user,password);
便可以成功獲得數據庫連接。
- 啟動類加載器加載DriverManager。
- DriverManager.getConnection時,通過SPI機制加載jar包中的mysql驅動。
- SPI中利用了線程上下文類加載器(應用程序類加載器)去加載類并創建對象。
4. SPI機制
- 需要先定義一個接口,作為擴展的標準。
- 在 classpath 目錄下創建 META-INF/service 文件目錄。
- 在這個目錄下,創建以接口的全限定名命名的配置文件,文件內容是這個接口的實現類。
- 在應用程序里面,使用 ServiceLoad.load(),就可以根據接口名稱找到 classpath 所有的擴展類。
感謝您的閱讀!如果文章中有任何問題或不足之處,歡迎及時指出,您的反饋將幫助我不斷改進與完善。期待與您共同探討技術,共同進步!