每个类都会使用自己的类加载器(即加载自身的类加载)来去加载其它类(指的是所依赖的类)
如果ClassX引用了ClassY,那么ClassX的类加载器就回去加载ClassY(前提是Class尚未被加载)
线程上下文类加载(Context ClassLoader)
线程上下文类加载器是从JDK1.2开始引用的,getContextClassLoader()与setContextClassLoader(ClassLoader cl)
用来分别获取和设置上下文类加载器
如果没有通过setContextClassLoader(ClassLoader cl)进行设置的话,线程将继承其父线程的上下文类加载器.
java应用运行时的初始线程的上下文类加载器是系统类加载器.在线程中运行的代码可以通过该类加载器来加载类与资源
线程上下文类加载器的重要性:
父ClassLoader可以使用当前线程Thread.currentThread().getContextClassLoader()所指定的ClassLoader加载的类
这就改变了父ClassLoader不能使用子ClassLoader或其它没有直接父子关系的ClassLoader加载的类的情况,即改变了双亲委托模型
线程上下文类加载器就是当前线程的Current ClassLoader
SPI: service provider interface 服务提供者接口
双亲委托模型下,类加载是由上至下的,即下层的类加载器会委托上层进行加载.但是对于SPI来说,有些接口是JAVA核心库提供的
而JAVA核心库是由启动类加载器来加载的,而这些接口的实现却来自不同的jar包(厂商提供),Java的启动类加载器时不会加载其它来源的jar包
这样传统的双亲委托模型就无法满足SPI的要求,而通过给当前线程设置上下文类加载器,就可以由设置的上下文类加载器来实现对于接口实现类的加载
线程上下文类加载器的一般使用模式(获取,使用,还原)
//伪代码
ClassLoader classLoader =Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(targetTccl);
myMethod();
} finally {
Thread.currentThread().setContextClassLoader(classLoader);
}
myMethod()里面则调用了Thread.currentThread().getContextClassLoader(),获取当前线程的上下文类加载器做某些事情
如果一个类由类加载器A加载,那么这个类的依赖类也是由相同的类加载器加载的(如果该依赖类之前没有被加载过)
contextClassLoader的作用就是为了破坏java的类加载委托机制
当高层提供了统一的接口让底层区实现,同时又要在高层加载(或实例化底层的类时),就必须要通过线程上下文类加载器来帮助高层的ClassLoader找到并加载该类
public class MyTest26 {
public static void main(String[] args) {
//Thread.currentThread().setContextClassLoader(MyTest26.class.getClassLoader().getParent());
ServiceLoader<Driver> loader =ServiceLoader.load(Driver.class);
Iterator<Driver> iterator = loader.iterator();
while(iterator.hasNext()){
Driver driver = iterator.next();
System.out.println("driver: "+driver.getClass()+" , loader: "+driver.getClass().getClassLoader());
}
System.out.println("当前线程上下文类加载器: "+Thread.currentThread().getContextClassLoader());
System.out.println("ServiceLoader 的类加载器: " +ServiceLoader.class.getClassLoader());
}
}
输出结果:
driver: class com.mysql.jdbc.Driver , loader: sun.misc.Launcher
A
p
p
C
l
a
s
s
L
o
a
d
e
r
@
18
b
4
a
a
c
2
d
r
i
v
e
r
:
c
l
a
s
s
c
o
m
.
m
y
s
q
l
.
f
a
b
r
i
c
.
j
d
b
c
.
F
a
b
r
i
c
M
y
S
Q
L
D
r
i
v
e
r
,
l
o
a
d
e
r
:
s
u
n
.
m
i
s
c
.
L
a
u
n
c
h
e
r
AppClassLoader@18b4aac2 driver: class com.mysql.fabric.jdbc.FabricMySQLDriver , loader: sun.misc.Launcher
AppClassLoader@18b4aac2driver:classcom.mysql.fabric.jdbc.FabricMySQLDriver,loader:sun.misc.LauncherAppClassLoader@18b4aac2
当前线程上下文类加载器: sun.misc.Launcher$AppClassLoader@18b4aac2
ServiceLoader 的类加载器: null
结果分析:
先看一下ServiceLoader的doc文档:
首先定义了两个名词
service:一个众所周知的接口和抽象类的集合
service provider:这个集合的具体实现
例如:service是JDK定义的Driver接口,而service provider是各个数据库厂商驱动的具体实现
而service provider 需要提供一个无参的构造方法这样它们就可以在被加载的时候实例化,同时service provider还需要提供一个provider-configuration文件.这个文件被放置在资源路径META-INF/services目录下,文件的名字是service的全限定名.文件的内容是service provider的全限定名.
我们在看一下具体的源代码实现:首先,获取当前线程的上下文类加载器也就是系统类加载器
最终接口调用获取的就是META-INF路径下的配置
public class MyTest27 {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
System.out.println(System.getProperty("jdbc.drivers"));
Class.forName("com.mysql.jdbc.Driver");
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mytestdb","username","password");
}
}
我们看一下jdbc的Class.forName(“com.mysql.jdbc.Driver”);到底做了什么事情
@CallerSensitive
public static Class<?> forName(String className)
throws ClassNotFoundException {
//获取调用者的Class对象,即MyTest27
Class<?> caller = Reflection.getCallerClass();
//使用调用者的类加载器加载com.mysql.jdbc.Driver并初始化
return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}
在看一下DriverManager.getConnection(“jdbc:mysql://localhost:3306/mytestdb”,“username”,“password”);方法
@CallerSensitive
public static Connection getConnection(String url,
String user, String password) throws SQLException {
java.util.Properties info = new java.util.Properties();
if (user != null) {
info.put("user", user);
}
if (password != null) {
info.put("password", password);
}
return (getConnection(url, info, Reflection.getCallerClass()));//获取调用者的Class对象即MyTest27
}
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.
*/
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");
}
看一下registeredDrivers是如何初始化的,根据静态代码块
static {
loadInitialDrivers();
println("JDBC DriverManager initialized");
}
看一下loadInitialDrivers();方法,首先获取系统属性jdbc.drivers,如果没有值则通过ServiceLoader.load(Driver.class)的方式加载,这个我们上述已经讲过了.所以现在再写JDBC,其实Class.forName(“com.mysql.jdbc.Driver”);是可以省略的.