双亲委托模型并不是一个强制性的约束模型,而是java设计者推荐给开发者的泪加载器实现方式。但是双亲委托模型存在着缺陷,它虽然解决了各个类加载器的基础类的统一问题,基础被称为基础,就是因为他们总是被用户代码调用,但是如果基础类又要调用回用户代码呢?那么在就会使用基础类的类加载器(启动类加载器)去加载用户的代码,而启动类加载器是加载java_home\lib目录下的。而用户代码都是保存在classpath下,根本就不可能加载到啊=。=其中这个方面最典型的就是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下的包???这,难为了吧,这违反了根据其定义的委托模型。那么他是如何做到的呢?
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.