雙親委派模型的破壞
雙親委託模型並不是一個強制性的約束模型,而是java設計者推薦給開發者的淚加載器實現方式。但是雙親委託模型存在着缺陷,它雖然解決了各個類加載器的基礎類的統一問題,基礎被稱爲基礎,就是因爲他們總是被用戶代碼調用,但是如果基礎類又要調用回用戶代碼呢?那麼在就會使用基礎類的類加載器(啓動類加載器)去加載用戶的代碼,而啓動類加載器是加載java_home\lib目錄下的。而用戶代碼都是保存在classpath下,根本就不可能加載到啊=。=其中這個方面最典型的就是jdbc對於雙親委派模型的破壞了。
JDBC對雙親委派模型的破壞
先來看一段代碼的描述:
public class JdbcTest {
public static void main(String[] args){
Connection connection = null;
try {
connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/test", "root", "awakeyo");
} catch (SQLException e) {
e.printStackTrace();
}
System.out.println(connection.getClass().getClassLoader());
System.out.println(Thread.currentThread().getContextClassLoader());
System.out.println(Connection.class.getClassLoader());
}
}
輸出結果:
很明顯我們發現Connection類的類加載器是啓動類加載器(應用類類加載器委託給啓動類加載器、雙清委託模型),因爲它輸出的是null,而第一第二行輸出語句輸出的是appClassloader應用程序類加載器.
因此,我們證明了java.sql.Connection是委託給應用加載器加載,但其子類的getConnection卻可以加載不在lib下的類???。根據類加載機制,當被裝載的類引用了另外一個類的時候,虛擬機就會使用裝載該類的類裝載器裝載被引用的類,這不就是要用應用類加載器加載classpath下的包???這,難爲了吧,這違反了根據其定義的委託模型。那麼他是如何做到的呢?
原因在於:getConnection方法
private static Connection getConnection(
String url, java.util.Properties info, Class<?> caller) throws SQLException {
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 + "\")");
SQLException reason = null;
for(DriverInfo aDriver : registeredDrivers) {
//isDriverAllowed對於mysql連接jar進行加載
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 (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");
}
獲取線程上下爲類加載器
callerCL = Thread.currentThread().getContextClassLoader();
isDriverAllowed對於mysql連接jar進行加載
isDriverAllowed(aDriver.driver, callerCL))
isDriverAllowed將傳入的Thread.currentThread().getContextClassLoader();拿到的應用類加載器用去Class.forName加載我們mysql連接jar,這樣子就可以加載到我們自己的mtsql連接jar
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;
}
爲什麼必須要破壞?
DriverManager::getConnection 方法需要根據參數傳進來的 url 從所有已經加載過的 Drivers 裏找到一個合適的 Driver 實現類去連接數據庫.
Driver 實現類在第三方 jar 裏, 要用 AppClassLoader 加載. 而 DriverManager 是 rt.jar 裏的類, 被 BootstrapClassLoader 加載, DriverManager 沒法用 BootstrapClassLoader 去加載 Driver 實現類(不再lib下), 所以只能破壞雙親委派模型, 用它下級的 AppClassLoader 去加載 Driver.