声明:本人写的这些,只是当时学习这些知识的理解,不代表是正确的,只是为了巩固记忆,好记忆不如烂笔头,如果哪里有问题,请指出,不喜勿喷,谢谢。
1. 什么是全盘负责委托机制
每个类都有自己的类加载器,那么负责加载当前类的类加载器也会去加载当前类中引用的其他类,前提是引用的类没有被加载过。
例如ClassA中有个变量 ClassB,那么加载ClassA的类加载器会去加载ClassB,如果找不到ClassB,则异常。
2. 为什么需要有线程上下文类加载器
jvm推荐我们使用双亲委托机制,主要是保证了相同的类不会被重复加载。但是,在jdk1.2之后,提出了线程上下文类加载器的概念,目的是为了打破双亲委托机制,因为在某些场景下(例如:JNDI,JDBC....)等等SPI场景中,关于什么是SPI(服务发现接口),可以参考之前写的文档资料(https://www.cnblogs.com/duguxiaobiao/p/12183135.html),使用双亲委托机制无法实现,那么为什么无法实现呢?
2.1 使用JDBC的例子,分析为什么双亲委托机制不能实现要求
原生的JDBC的使用,获取数据库连接使用的是 Connection conn = DriverManager.getConnection(xx,xx,xx);很明显,Connection是jdk提供的接口,具体的实现是我们的厂商例如mysql 实现,加入到项目中,那么设想一下,DriverManager.getConnection(xx,xx,xx);该方法肯定是使用的mysql的jar包,返回了mysql实现的Connection对象,那么加载DriverManager类是由启动类加载器加载,根据上面的全盘负责委托机制来说,启动类加载器会去加载MySql的jar包,很明显,找不到。所以使用双亲委托机制来说,无法实现该SPI场景的需求。
2.2 线程上下文类加载器的作用
双亲委托机制:子加载器对应的命名空间包含了父加载器,所以可以实现子容器访问父容器
线程上下文类加载器:使用该类加载器,可以实现 父容器访问子容器场景,主要设置好上下文类加载器即可。
3. 线程上下文类加载器的使用
3.1 线程上下文类加载器使用API
- 获取当前线程的上下文类加载器:Thread.currentThread().getContextClassLoader();
- 设置当前线程的上下文类加载器:Thread.currentThread().setContextClassLoader(ClassLoader cl);
3.2 线程上下文类加载器的特征
- 如果没有设置 setContextClassLoader(),那么线程将继承父线程的上下文类加载器,这段可以通过Thread.init()方法中可以看出
- Java应用运行时初始上下文类加载器是系统类加载器,可以在源码:Launcher类的构造方法中,在实例化系统类加载器后,将之设置为上下文类加载器。
3.3 线程上下文类加载器使用的通用写法
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(null);
//其他执行事件
doSomthing();
} finally {
Thread.currentThread().setContextClassLoader(contextClassLoader);
}
4. 借助JDBC源码分析上下文类加载器的使用
4.1 示例代码
Class.forName("com.mysql.jdbc.Driver");
Connection connection = DriverManager.getConnection("xxx", "xx", "xx");
4.2 源码分析
4.2.1 首先解析第一句 Class.forName("com.mysql.jdbc.Driver");
这里就不介绍了Class.forName()源码了,这行代码表示初始化 Driver类,对Driver类的主动使用,就会导致Driver的静态代码块执行,那么我们进入到Driver类中,看是否有需要初始化调用的静态代码块。
public Driver() throws SQLException {
}
static {
try {
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
可以看到,存在静态代码块,那么进入到静态代码块中,我们来解析 DriverManager.registerDriver(new Driver());
4.2.2 DriverManager.registerDriver(new Driver());
可能有的朋友查看该代码源码时,直接就会进入到DriverManager类中查看registerDriver(),其实跟解析第一行一行,主动调用类的静态方法,会导致累的初始化,执行 DriverManager中的静态代码块。所以我们需要先看下面的源码:
static {
//首先会添加初始化加载drivers,这里引入了ServiceLoader
loadInitialDrivers();
println("JDBC DriverManager initialized");
}
//因为我们没有设置 jdbc.drivers属性,所以这里只展示关键代码,对于其他不影响流程的代码有所删减,具体的可以看源码
private static void loadInitialDrivers() {
String drivers;
try {
drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
public String run() {
return System.getProperty("jdbc.drivers");
}
});
} catch (Exception ex) {
drivers = null;
}
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
//主要的引入各个厂家的Driver类是的服务是在这里加入的
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator();
try{
while(driversIterator.hasNext()) {
driversIterator.next();
}
} catch(Throwable t) {
// Do nothing
}
return null;
}
});
}
如果熟悉针对SPI场景的服务加载方式ServiceLoader使用的,当看到这经典的几行的代码时,就知道具体的加载方式了,如果有对ServiceLoader或者SPI不是很熟悉的,可以先阅读或者百度下相关文档(https://www.cnblogs.com/duguxiaobiao/p/12183135.html),那么通过下面这行代码就可以将mysql依赖加载到内存中了。
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
那么是怎么在DriverManager对应的类加载器启动类加载器中加载到mysql jar包的呢,下面来分析 ServiceLaoder.load()方法,go
4.2.3 ServiceLaoder.load()
public static <S> ServiceLoader<S> load(Class<S> service) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
public static <S> ServiceLoader<S> load(Class<S> service,
ClassLoader loader)
{
return new ServiceLoader<>(service, loader);
}
public void reload() {
providers.clear();
lookupIterator = new LazyIterator(service, loader);
}
private ServiceLoader(Class<S> svc, ClassLoader cl) {
service = Objects.requireNonNull(svc, "Service interface cannot be null");
loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
reload();
}
//定义的内部类
private class LazyIterator implements Iterator<S>
{
Class<S> service;
ClassLoader loader;
Enumeration<URL> configs = null;
Iterator<String> pending = null;
String nextName = null;
private LazyIterator(Class<S> service, ClassLoader loader) {
this.service = service;
this.loader = loader;
}
private boolean hasNextService() {
if (nextName != null) {
return true;
}
if (configs == null) {
try {
String fullName = PREFIX + service.getName();
if (loader == null)
configs = ClassLoader.getSystemResources(fullName);
else
configs = loader.getResources(fullName);
} catch (IOException x) {
fail(service, "Error locating configuration files", x);
}
}
while ((pending == null) || !pending.hasNext()) {
if (!configs.hasMoreElements()) {
return false;
}
pending = parse(service, configs.nextElement());
}
nextName = pending.next();
return true;
}
private S nextService() {
if (!hasNextService())
throw new NoSuchElementException();
String cn = nextName;
nextName = null;
Class<?> c = null;
try {
c = Class.forName(cn, false, loader);
} catch (ClassNotFoundException x) {
fail(service,
"Provider " + cn + " not found");
}
if (!service.isAssignableFrom(c)) {
fail(service,
"Provider " + cn + " not a subtype");
}
try {
S p = service.cast(c.newInstance());
providers.put(cn, p);
return p;
} catch (Throwable x) {
fail(service,
"Provider " + cn + " could not be instantiated",
x);
}
throw new Error(); // This cannot happen
}
public boolean hasNext() {
if (acc == null) {
return hasNextService();
} else {
PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
public Boolean run() { return hasNextService(); }
};
return AccessController.doPrivileged(action, acc);
}
}
public S next() {
if (acc == null) {
return nextService();
} else {
PrivilegedAction<S> action = new PrivilegedAction<S>() {
public S run() { return nextService(); }
};
return AccessController.doPrivileged(action, acc);
}
}
public void remove() {
throw new UnsupportedOperationException();
}
}
查阅源码,我们可以看出当我们执行 ServiceLaoder.load()的时候,首先会获取当前线程的上下文类加载器。而且在构造方法中也可以看到,如果获取的上下文类加载器为空时,也会使用默认的系统类加载器,而默认设置当前线程的上下文类加载器的时候,默认运行时也是系统类加载器作为上下文类加载器,所以先肯定一点,后续加载类的类加载器肯定是 系统类加载器。
致此,通过 ServiceLoader
Iterator<Driver> driversIterator = loadedDrivers.iterator();
try{
while(driversIterator.hasNext()) {
driversIterator.next();
}
} catch(Throwable t) {
// Do nothing
}
4.2.4 driversIterator.hasNext(); driversIterator.next();
从ServiceLoader.iterator()源码中可以看出, driversIterator.hasNext()其实是调用了 load()时候创建的 LazyIterator.hasNext()方法
public Iterator<S> iterator() {
return new Iterator<S>() {
Iterator<Map.Entry<String,S>> knownProviders = providers.entrySet().iterator();
public boolean hasNext() {
if (knownProviders.hasNext())
return true;
return lookupIterator.hasNext();
}
public S next() {
if (knownProviders.hasNext())
return knownProviders.next().getValue();
return lookupIterator.next();
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
LazyIterator.hasNext() 最终调用了 LazyIterator.hasNextService()方法,致此,我们就可以看到ServiceLoader是如何在指定目录下获取到指定类名对应的实现类全类名信息的。有兴趣的可以看看。
我们的重点是在 LazyIterator.next()方法,该方法最终调用了 LazyIterator.nextService()方法,在该方法中我们可以看到如何将mysql的Driver实现类使用上下文类加载器所加载到内存中。
private S nextService() {
if (!hasNextService())
throw new NoSuchElementException();
//在遍历的时候获取的当前item的文件内容
String cn = nextName;
nextName = null;
Class<?> c = null;
try {
//是否初始化参数为false,表示这里只加载类,不初始化
c = Class.forName(cn, false, loader);
} catch (ClassNotFoundException x) {
fail(service,
"Provider " + cn + " not found");
}
if (!service.isAssignableFrom(c)) {
fail(service,
"Provider " + cn + " not a subtype");
}
try {
//这里才将类初始化,触发mysql Driver类的静态代码块
S p = service.cast(c.newInstance());
providers.put(cn, p);
return p;
} catch (Throwable x) {
fail(service,
"Provider " + cn + " could not be instantiated",
x);
}
throw new Error(); // This cannot happen
}
上面代码说简单点,就是在 hasNext()中找到待实例化的全类名,而在 next()中实例化该类。
注意:这里有一个问题 我们在最之前的代码中,使用了Class.forName()将mysql驱动的Driver类加载了,这里又重复了一次,那我们的第一步岂不是多余的操作。
这个疑问很好,也是正确的,所以在新版本的JDBC处理上,已经使用了SPI方式的ServiceLoader加载方式,不在需要第一步骤的手动加载初始化具体的驱动全类名了。
既然已经触发了mysql Driver累的初始化,那么跟最开始一样,不再重复,这回DriverManager静态代码块已经执行完毕,可以真正的执行 DriverManager.registerDriver(new Driver());了,其实最终也是调用的 DriverManager.registerDriver()
4.2.5 DriverManager.registerDriver();
public static synchronized void registerDriver(java.sql.Driver driver,
DriverAction da)
throws SQLException {
/* Register the driver if it has not already been added to our list */
if(driver != null) {
//加载到缓存中
registeredDrivers.addIfAbsent(new DriverInfo(driver, da));
} else {
// This is for compatibility with the original DriverManager
throw new NullPointerException();
}
println("registerDriver: " + driver);
}
从上述代码上看,其实做的东西很简单,就是判断该Driver如果在缓存中没有就添加到缓存中而已。
致此,第一行代码解析完毕,后面的具体如何获取数据库连接的,跟当前文章想表达的偏离了,所以不再继续了,over