www.jianshu.com/p/60dbd8009…
java
資源來源《深刻理解Java虛擬機》mysql
雙親委派模型的第一次「被破壞」其實發生在雙親委派模型出現以前--即JDK1.2發佈以前。因爲雙親委派模型是在JDK1.2以後才被引入的,而類加載器和抽象類java.lang.ClassLoader則是JDK1.0時候就已經存在,面對已經存在 的用戶自定義類加載器的實現代碼,Java設計者引入雙親委派模型時不得不作出一些妥協。爲了向前兼容,JDK1.2以後的java.lang.ClassLoader添加了一個新的proceted方法findClass(),在此以前,用戶去繼承java.lang.ClassLoader的惟一目的就是重寫loadClass()方法,由於虛擬在進行類加載的時候會調用加載器的私有方法loadClassInternal(),而這個方法的惟一邏輯就是去調用本身的loadClass()。JDK1.2以後已再也不提倡用戶再去覆蓋loadClass()方法,應當把本身的類加載邏輯寫到findClass()方法中,在loadClass()方法的邏輯裏,若是父類加載器加載失敗,則會調用本身的findClass()方法來完成加載,這樣就能夠保證新寫出來的類加載器是符合雙親委派模型的。
雙親委派模型的第二次「被破壞」是這個模型自身的缺陷所致使的,雙親委派模型很好地解決了各個類加載器的基礎類統一問題(越基礎的類由越上層的加載器進行加載),基礎類之因此被稱爲「基礎」,是由於它們老是做爲被調用代碼調用的API。可是,若是基礎類又要調用用戶的代碼,那該怎麼辦呢。
這並不是是不可能的事情,一個典型的例子即是JNDI服務,它的代碼由啓動類加載器去加載(在JDK1.3時放進rt.jar),但JNDI的目的就是對資源進行集中管理和查找,它須要調用獨立廠商實現部部署在應用程序的classpath下的JNDI接口提供者(SPI, Service Provider Interface)的代碼,但啓動類加載器不可能「認識」之些代碼,該怎麼辦?
爲了解決這個困境,Java設計團隊只好引入了一個不太優雅的設計:線程上下文件類加載器(Thread Context ClassLoader)。這個類加載器能夠經過java.lang.Thread類的setContextClassLoader()方法進行設置,若是建立線程時還未設置,它將會從父線程中繼承一個;若是在應用程序的全局範圍內都沒有設置過,那麼這個類加載器默認就是應用程序類加載器。了有線程上下文類加載器,JNDI服務使用這個線程上下文類加載器去加載所須要的SPI代碼,也就是父類加載器請求子類加載器去完成類加載動做,這種行爲實際上就是打通了雙親委派模型的層次結構來逆向使用類加載器,已經違背了雙親委派模型,但這也是迫不得已的事情。Java中全部涉及SPI的加載動做基本上都採用這種方式,例如JNDI,JDBC,JCE,JAXB和JBI等。sql
第一種比較簡單,這裏就不說啦。數據庫
關於第二種,看看JDBC中是怎麼實現的吧bash
首先,理解一下爲何JDBC須要破壞雙親委派模式,緣由是原生的JDBC中Driver驅動自己只是一個接口,並無具體的實現,具體的實現是由不一樣數據庫類型去實現的。例如,MySQL的mysql-connector-app
//callerCL爲空的時候,其實說明這個ClassLoader是啓動類加載器,可是這個啓動類加載並不能識別rt.jar以外的類,這個時候就把callerCL賦值爲Thread.currentThread().getContextClassLoader();也就是應用程序啓動類ide
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爲空的時候,其實說明這個ClassLoader是啓動類加載器,可是這個啓動類加載並不能識別rt.jar以外的類,這個時候就把callerCL賦值爲Thread.currentThread().getContextClassLoader();也就是應用程序啓動類 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"); } 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; } 複製代碼
但願個人理解能給你帶來幫助,若有錯誤的地方請指正。ui