資源來源《深入理解Java虛擬機(jī)》
雙親委派模型的第一次“被破壞”其實(shí)發(fā)生在雙親委派模型出現(xiàn)之前--即JDK1.2發(fā)布之前匙瘪。由于雙親委派模型是在JDK1.2之后才被引入的狞贱,而類加載器和抽象類java.lang.ClassLoader則是JDK1.0時候就已經(jīng)存在雀彼,面對已經(jīng)存在 的用戶自定義類加載器的實(shí)現(xiàn)代碼埃仪,Java設(shè)計(jì)者引入雙親委派模型時不得不做出一些妥協(xié)沙绝。為了向前兼容捌归,JDK1.2之后的java.lang.ClassLoader添加了一個新的proceted方法findClass()亿絮,在此之前告喊,用戶去繼承java.lang.ClassLoader的唯一目的就是重寫loadClass()方法,因?yàn)樘摂M在進(jìn)行類加載的時候會調(diào)用加載器的私有方法loadClassInternal()派昧,而這個方法的唯一邏輯就是去調(diào)用自己的loadClass()黔姜。JDK1.2之后已不再提倡用戶再去覆蓋loadClass()方法,應(yīng)當(dāng)把自己的類加載邏輯寫到findClass()方法中蒂萎,在loadClass()方法的邏輯里秆吵,如果父類加載器加載失敗,則會調(diào)用自己的findClass()方法來完成加載五慈,這樣就可以保證新寫出來的類加載器是符合雙親委派模型的纳寂。
雙親委派模型的第二次“被破壞”是這個模型自身的缺陷所導(dǎo)致的,雙親委派模型很好地解決了各個類加載器的基礎(chǔ)類統(tǒng)一問題(越基礎(chǔ)的類由越上層的加載器進(jìn)行加載)泻拦,基礎(chǔ)類之所以被稱為“基礎(chǔ)”毙芜,是因?yàn)樗鼈兛偸亲鳛楸徽{(diào)用代碼調(diào)用的API。但是争拐,如果基礎(chǔ)類又要調(diào)用用戶的代碼腋粥,那該怎么辦呢。
這并非是不可能的事情架曹,一個典型的例子便是JNDI服務(wù)隘冲,它的代碼由啟動類加載器去加載(在JDK1.3時放進(jìn)rt.jar),但JNDI的目的就是對資源進(jìn)行集中管理和查找绑雄,它需要調(diào)用獨(dú)立廠商實(shí)現(xiàn)部部署在應(yīng)用程序的classpath下的JNDI接口提供者(SPI, Service Provider Interface)的代碼展辞,但啟動類加載器不可能“認(rèn)識”之些代碼,該怎么辦万牺?
為了解決這個困境罗珍,Java設(shè)計(jì)團(tuán)隊(duì)只好引入了一個不太優(yōu)雅的設(shè)計(jì):線程上下文件類加載器(Thread Context ClassLoader)。這個類加載器可以通過java.lang.Thread類的setContextClassLoader()方法進(jìn)行設(shè)置杏愤,如果創(chuàng)建線程時還未設(shè)置靡砌,它將會從父線程中繼承一個已脓;如果在應(yīng)用程序的全局范圍內(nèi)都沒有設(shè)置過珊楼,那么這個類加載器默認(rèn)就是應(yīng)用程序類加載器。了有線程上下文類加載器度液,JNDI服務(wù)使用這個線程上下文類加載器去加載所需要的SPI代碼厕宗,也就是父類加載器請求子類加載器去完成類加載動作画舌,這種行為實(shí)際上就是打通了雙親委派模型的層次結(jié)構(gòu)來逆向使用類加載器,已經(jīng)違背了雙親委派模型已慢,但這也是無可奈何的事情曲聂。Java中所有涉及SPI的加載動作基本上都采用這種方式,例如JNDI,JDBC,JCE,JAXB和JBI等佑惠。
第一種比較簡單朋腋,這里就不說啦。
關(guān)于第二種膜楷,看看JDBC中是怎么實(shí)現(xiàn)的吧
首先旭咽,理解一下為什么JDBC需要破壞雙親委派模式,原因是原生的JDBC中Driver驅(qū)動本身只是一個接口赌厅,并沒有具體的實(shí)現(xiàn)穷绵,具體的實(shí)現(xiàn)是由不同數(shù)據(jù)庫類型去實(shí)現(xiàn)的。例如特愿,MySQL的mysql-connector-.jar中的Driver類具體實(shí)現(xiàn)的仲墨。 原生的JDBC中的類是放在rt.jar包的,是由啟動類加載器進(jìn)行類加載的揍障,在JDBC中的Driver類中需要動態(tài)去加載不同數(shù)據(jù)庫類型的Driver類目养,而mysql-connector-.jar中的Driver類是用戶自己寫的代碼,那啟動類加載器肯定是不能進(jìn)行加載的毒嫡,既然是自己編寫的代碼混稽,那就需要由應(yīng)用程序啟動類去進(jìn)行類加載。于是乎审胚,這個時候就引入線程上下文件類加載器(Thread Context ClassLoader)匈勋。有了這個東西之后,程序就可以把原本需要由啟動類加載器進(jìn)行加載的類膳叨,由應(yīng)用程序類加載器去進(jìn)行加載了洽洁。下面看看JDBC中是怎么去應(yīng)用的呢
//callerCL為空的時候,其實(shí)說明這個ClassLoader是啟動類加載器菲嘴,但是這個啟動類加載并不能識別rt.jar之外的類饿自,這個時候就把callerCL賦值為Thread.currentThread().getContextClassLoader();也就是應(yīng)用程序啟動類
private static Connection getConnection(
String url, java.util.Properties info, Class<?> caller) throws SQLException {
/*
* When callerCl is null, we should check the application's
* (which is invoking this class indirectly)
* classloader, so that the JDBC driver class outside rt.jar
* can be loaded from here.
*/
//callerCL為空的時候,其實(shí)說明這個ClassLoader是啟動類加載器龄坪,但是這個啟動類加載并不能識別rt.jar之外的類昭雌,這個時候就把callerCL賦值為Thread.currentThread().getContextClassLoader();也就是應(yīng)用程序啟動類
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.
//繼續(xù)看這里
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");
}
private static boolean isDriverAllowed(Driver driver, ClassLoader classLoader) {
boolean result = false;
if(driver != null) {
Class<?> aClass = null;
try {
//這一步會對類進(jìn)行初始化的動作,而初始化之前自然也要進(jìn)行的類的加載工作
aClass = Class.forName(driver.getClass().getName(), true, classLoader);
} catch (Exception ex) {
result = false;
}
result = ( aClass == driver.getClass() ) ? true : false;
}
return result;
}
希望我的理解能給你帶來幫助健田,如有錯誤的地方請指正烛卧。