破坏双亲委派机制的那些事
前言
今天重讀《深入理解Java虛擬》這本書,讀到破壞雙親委派機(jī)制這一小節(jié),其中有一段話,如下
雙親委派模型的第二次“被破壞”是由這個(gè)模型自身的缺陷所導(dǎo)致的,雙親委派很好地解決了各個(gè)類加載器的基礎(chǔ)類的統(tǒng)一問題(越基礎(chǔ)的類由越上層的加載器進(jìn)行加載),基礎(chǔ)類之所以稱為“基礎(chǔ)”,是因?yàn)樗鼈兛偸亲鳛楸挥脩舸a調(diào)用的API,但世事往往沒有絕對(duì)的完美,如果基礎(chǔ)類又要調(diào)用回用戶的代碼,那該怎么辦?
這并非是不可能的事情,一個(gè)典型的例子便是JNDI服務(wù),JNDI現(xiàn)在已經(jīng)是Java的標(biāo)準(zhǔn)服務(wù),它的代碼由啟動(dòng)類加載器去加載(在JDK 1.3時(shí)放進(jìn)去的rt.jar),但JNDI的目的就是對(duì)資源進(jìn)行集中管理和查找,它需要調(diào)用由獨(dú)立廠商實(shí)現(xiàn)并部署在應(yīng)用程序的ClassPath下的JNDI接口提供者(SPI,Service Provider Interface)的代碼,但啟動(dòng)類加載器不可能“認(rèn)識(shí)”這些代碼啊!那該怎么辦?
為了解決這個(gè)問題,Java設(shè)計(jì)團(tuán)隊(duì)只好引入了一個(gè)不太優(yōu)雅的設(shè)計(jì):線程上下文類加載器(Thread Context ClassLoader)。這個(gè)類加載器可以通過java.lang.Thread類的setContextClassLoaser()方法進(jìn)行設(shè)置,如果創(chuàng)建線程時(shí)還未設(shè)置,它將會(huì)從父線程中繼承一個(gè),如果在應(yīng)用程序的全局范圍內(nèi)都沒有設(shè)置過的話,那這個(gè)類加載器默認(rèn)就是應(yīng)用程序類加載器。
有了線程上下文類加載器,就可以做一些“舞弊”的事情了,JNDI服務(wù)使用這個(gè)線程上下文類加載器去加載所需要的SPI代碼,也就是父類加載器請(qǐng)求子類加載器去完成類加載的動(dòng)作,這種行為實(shí)際上就是打通了雙親委派模型的層次結(jié)構(gòu)來逆向使用類加載器,實(shí)際上已經(jīng)違背了雙親委派模型的一般性原則,但這也是無可奈何的事情。Java中所有涉及SPI的加載動(dòng)作基本上都采用這種方式,例如JNDI、JDBC、JCE、JAXB和JBI等。
一直已來對(duì)破壞雙親委派機(jī)制的理解也僅限于此,今日再看到這段話對(duì)誘惑線程上下文類加載器如何破壞雙親委派不甚疑惑,故查找網(wǎng)上資料結(jié)合自身理解作此文以記錄。
JDBC中的逆雙親委派機(jī)制源碼分析
感謝 真正理解線程上下文類加載器(多案例分析)的啟發(fā)。
首先,我們看一下JDBC連接數(shù)據(jù)庫(kù)中加載驅(qū)動(dòng)并獲取連接的代碼。
以上就是JDBC連接數(shù)據(jù)并獲取連接的代碼,那調(diào)用這些的方法到底做了些什么呢?
首先,我們用Class.forName("com.mysql.jdbc.Driver")加載了驅(qū)動(dòng),Driver的源碼很簡(jiǎn)單,如下
我們用系統(tǒng)類加載器加載了com.mysql.jdbc.Driver,類初始化的時(shí)候執(zhí)行靜態(tài)代碼塊,靜態(tài)代碼塊中將new了一個(gè)Driver實(shí)例并將他注冊(cè)到DriverManager中。注意,這里的Driver實(shí)例的類加載器是系統(tǒng)類加載器。接下來,我們調(diào)用了DriverManager.getConnection(String url,
String user, String password),其源碼如下
其調(diào)用了另一段關(guān)鍵代碼,如下
private static Connection getConnection(String url, java.util.Properties info, Class<?> caller) throws SQLException {/** 這里要確保類加載不能是BootstrapClassLoader,* 因?yàn)锽ootstrapClassLoader不能加載到用戶類庫(kù)(JDBC驅(qū)動(dòng)為用戶類庫(kù))*/ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;synchronized(DriverManager.class) {// synchronize loading of the correct classloader.if (callerCL == null) {//獲取系統(tǒng)類加載器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;}小結(jié),此處線程上下文類加載器的作用主要用于校驗(yàn)存放的driver是否和調(diào)用時(shí)的一致由此判斷是否有權(quán)限獲取連接。
作者:NoSuchElementEx
鏈接:https://www.jianshu.com/p/bfa495467014
來源:簡(jiǎn)書
?
總結(jié)
以上是生活随笔為你收集整理的破坏双亲委派机制的那些事的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 类加载机制-双亲委派,破坏双亲委派--这
- 下一篇: JIT 编译器概述