背景
在前面聊到ClassLoader是如何工作的,有些时候ClassLoader的双亲委托机制不能完成一些特定的类加载任务,比如java提供一些SPI,由厂商来进行具体的实现,比如jdbc,各个数据库厂商根据java提供的SPI来实现各自数据库的连接;这些SPI都定义在核心类里,由bootstrap ClassLoader加载,而在SPI 接口中的代码经常需要加载具体的实现类,但厂商的具体实现又不能由bootstrap ClassLoader加载,那如何实现的呢?答案是contextClassLoader(线程上下文类加载器),下面来以jdbc为例看看到底是如何工作的
实现
先来看一小段jdbc获取连接的代码
Class.forName("com.mysql.jdbc.Driver");
DriverManager.getConnection("jdbc:mysql://localhost:3306/db", "user", "psw");
深挖一下上面的代码,首先Class.forName方法都知道是加载指定的类
- 那么就再看看forName方法吧
public static Class<?> forName(String className) throws ClassNotFoundException {
//通过Reflection.getCallerClass()方法获取到调用forName的class
Class<?> caller = Reflection.getCallerClass();
//调用forName0,传入的ClassLoader为加载caller的ClassLoader,forName0为native方法
return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}
- 再看看加载com.mysql.jdbc.Driver的时候会发生什么
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
public Driver() throws SQLException {
}
static {
try {
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can\'t register driver!");
}
}
}
注意到这里有个静态代码块,而class的forName方法默认对加载的类进行了链接操作,所以这里的静态代码块会被执行;静态代码块将com.mysql.jdbc.Driver注册到DriverManager,其实就是被DriverManager的静态成员registeredDrivers持有缓存起来,而DriverManager也在这个时候被加载了,根据ClassLoader的双亲委托机制,DriverManager由bootstrap ClassLoader加载;到这里class的forName方法就执行完了
- 下面执行getConnection(获取数据库连接)
public static Connection getConnection(String url,
String user, String password) throws SQLException {
//将用户名和密码封装到properties里
java.util.Properties info = new java.util.Properties();
if (user != null) {
info.put("user", user);
}
if (password != null) {
info.put("password", password);
}
//调用真正的获取连接的方法,指定caller的class为调用当前方法的类
return (getConnection(url, info, Reflection.getCallerClass()));
}
private static Connection getConnection(
String url, java.util.Properties info, Class<?> caller) throws SQLException {
//如果caller存在,则将ClassLoader设置为加载caller的ClassLoader
ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
synchronized(DriverManager.class) {
//如果ClassLoader不存在,那么ClassLoader设置为当前线程上下文的类加载器(getContextClassLoader)为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;
//循环注册了的Driver
for(DriverInfo aDriver : registeredDrivers) {
//如果callerClassLoader有权限加载aDriver.driver,即:判断两个driver是否由同一个ClassLoader加载的
if(isDriverAllowed(aDriver.driver, callerCL)) {
try {
println(" trying " + aDriver.driver.getClass().getName());
//通过之前注册的drive获取数据库连接
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;
}
throw new SQLException("No suitable driver found for "+ url, "08001");
}
小结
SPI加载实现类主要是通过Reflection.getCallerClass()和Thread.currentThread().getContextClassLoader()来避免双亲委托带来的尴尬;