破坏双亲委派机制的那些事
前言
今天重讀《深入理解Java虛擬》這本書,讀到破壞雙親委派機制這一小節,其中有一段話,如下
雙親委派模型的第二次“被破壞”是由這個模型自身的缺陷所導致的,雙親委派很好地解決了各個類加載器的基礎類的統一問題(越基礎的類由越上層的加載器進行加載),基礎類之所以稱為“基礎”,是因為它們總是作為被用戶代碼調用的API,但世事往往沒有絕對的完美,如果基礎類又要調用回用戶的代碼,那該怎么辦?
這并非是不可能的事情,一個典型的例子便是JNDI服務,JNDI現在已經是Java的標準服務,它的代碼由啟動類加載器去加載(在JDK 1.3時放進去的rt.jar),但JNDI的目的就是對資源進行集中管理和查找,它需要調用由獨立廠商實現并部署在應用程序的ClassPath下的JNDI接口提供者(SPI,Service Provider Interface)的代碼,但啟動類加載器不可能“認識”這些代碼啊!那該怎么辦?
為了解決這個問題,Java設計團隊只好引入了一個不太優雅的設計:線程上下文類加載器(Thread Context ClassLoader)。這個類加載器可以通過java.lang.Thread類的setContextClassLoaser()方法進行設置,如果創建線程時還未設置,它將會從父線程中繼承一個,如果在應用程序的全局范圍內都沒有設置過的話,那這個類加載器默認就是應用程序類加載器。
有了線程上下文類加載器,就可以做一些“舞弊”的事情了,JNDI服務使用這個線程上下文類加載器去加載所需要的SPI代碼,也就是父類加載器請求子類加載器去完成類加載的動作,這種行為實際上就是打通了雙親委派模型的層次結構來逆向使用類加載器,實際上已經違背了雙親委派模型的一般性原則,但這也是無可奈何的事情。Java中所有涉及SPI的加載動作基本上都采用這種方式,例如JNDI、JDBC、JCE、JAXB和JBI等。
一直已來對破壞雙親委派機制的理解也僅限于此,今日再看到這段話對誘惑線程上下文類加載器如何破壞雙親委派不甚疑惑,故查找網上資料結合自身理解作此文以記錄。
JDBC中的逆雙親委派機制源碼分析
感謝 真正理解線程上下文類加載器(多案例分析)的啟發。
首先,我們看一下JDBC連接數據庫中加載驅動并獲取連接的代碼。
以上就是JDBC連接數據并獲取連接的代碼,那調用這些的方法到底做了些什么呢?
首先,我們用Class.forName("com.mysql.jdbc.Driver")加載了驅動,Driver的源碼很簡單,如下
我們用系統類加載器加載了com.mysql.jdbc.Driver,類初始化的時候執行靜態代碼塊,靜態代碼塊中將new了一個Driver實例并將他注冊到DriverManager中。注意,這里的Driver實例的類加載器是系統類加載器。接下來,我們調用了DriverManager.getConnection(String url,
String user, String password),其源碼如下
其調用了另一段關鍵代碼,如下
private static Connection getConnection(String url, java.util.Properties info, Class<?> caller) throws SQLException {/** 這里要確保類加載不能是BootstrapClassLoader,* 因為BootstrapClassLoader不能加載到用戶類庫(JDBC驅動為用戶類庫)*/ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;synchronized(DriverManager.class) {// synchronize loading of the correct classloader.if (callerCL == null) {//獲取系統類加載器callerCL = Thread.currentThread().getContextClassLoader();}}if(url == null) {throw new SQLException("The url cannot be null", "08001");}println("DriverManager.getConnection(\"" + url + "\")");// Walk through the loaded registeredDrivers attempting to make a connection.// Remember the first exception that gets raised so we can reraise it.SQLException reason = null;for(DriverInfo aDriver : registeredDrivers) {// If the caller does not have permission to load the driver then// skip it.if(isDriverAllowed(aDriver.driver, callerCL)) {try {println(" trying " + aDriver.driver.getClass().getName());Connection con = aDriver.driver.connect(url, info);if (con != null) {// Success!println("getConnection returning " + aDriver.driver.getClass().getName());return (con);}} catch (SQLException ex) {if (reason == null) {reason = ex;}}} else {println(" skipping: " + aDriver.getClass().getName());}}// if we got here nobody could connect.if (reason != null) {println("getConnection failed: " + reason);throw reason;}println("getConnection: no suitable driver found for "+ url);throw new SQLException("No suitable driver found for "+ url, "08001");}這段代碼并沒有使用ClassLoader
private static boolean isDriverAllowed(Driver driver, ClassLoader classLoader) {boolean result = false;if(driver != null) {Class<?> aClass = null;try {aClass = Class.forName(driver.getClass().getName(), true, classLoader);} catch (Exception ex) {result = false;}//類加載器與類相同才能確定==result = ( aClass == driver.getClass() ) ? true : false;}return result;}小結,此處線程上下文類加載器的作用主要用于校驗存放的driver是否和調用時的一致由此判斷是否有權限獲取連接。
作者:NoSuchElementEx
鏈接:https://www.jianshu.com/p/bfa495467014
來源:簡書
?
總結
以上是生活随笔為你收集整理的破坏双亲委派机制的那些事的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 类加载机制-双亲委派,破坏双亲委派--这
- 下一篇: JIT 编译器概述