java 上下文加载器_理解 Java Thread ContextClassLoader(线程上下文类加载器)

Java的类加载机制采用双亲委派模型,但为了解决基础类调用用户代码的问题,引入了Thread ContextClassLoader。它允许子ClassLoader加载父ClassLoader无法加载的类。在JDBC Driver加载中,Thread ContextClassLoader用于加载应用程序引入的数据库驱动,使得SPI机制能够正常工作。
摘要由CSDN通过智能技术生成

为什么需要ContextClassLoader

Java中的类加载机制是双亲委派模型,即按照AppClassLoader → SystemClassLoader → BootstrapClassLoader 的顺序,子ClassLoader将一个类加载的任务委托给父ClassLoader(父ClassLoader会再委托给父的父ClassLoader)来完成,只有父ClassLoader无法完成该类的加载时,子ClassLoader才会尝试自己去加载该类。所以越基础的类由越上层的ClassLoader进行加载,但如果基础类又要调用回用户的代码,那该怎么办?

为了解决这个问题,Java设计团队只好引入了一个不太优雅的设计:Thread ContextClassLoader(线程上下文类加载器)。这个ClassLoader可以通过 java.lang.Thread类的setContextClassLoaser()方法进行设置;如果创建线程时没有设置,则它会从父线程中继承(见以下Thread的源码);如果在应用程序的全局范围内都没有设置过的话,那这个类加载器默认为AppClassLoader(见以下代码验证)。

public class Thread implements Runnable {

// 这里省略了无关代码

private void init(ThreadGroup g, Runnable target, String name,

long stackSize, AccessControlContext acc,

boolean inheritThreadLocals) {

// 这里省略了无关代码

if (security == null || isCCLOverridden(parent.getClass()))

this.contextClassLoader = parent.getContextClassLoader();

else

this.contextClassLoader = parent.contextClassLoader; // 继承父线程的 上下文类加载器

// 这里省略了无关代码

}

public Thread(Runnable target) {

init(null, target, "Thread-" + nextThreadNum(), 0);

}

// 这里省略了无关代码

}

package com.bluesky.jvm.classloader;

public class ContextClassLoaderTest {

public static void main(String[] args) {

ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();

System.err.println(contextClassLoader); // 输出:sun.misc.Launcher$AppClassLoader@4e0e2f2a

}

}

有了Thread ContextClassLoader,就可以实现父ClassLoader让子ClassLoader去完成一个类的加载任务,即父ClassLoader加载的类中,可以使用ContextClassLoader去加载其无法加载的类)。

Thread ContextClassLoader 在 JDBC Driver 加载中的使用

Java 中所有涉及SPI机制的类加载基本上都是采用这种方式,最常见的就是JDBC Driver的加载。

JDBC是Java提出的一个有关数据库访问和操作的一个标准,也就是定义了一系列接口。不同的数据库厂商(Oracle、MySQL、PostgreSQL等)提供对该接口的实现,即他们提供的Driver驱动包。Java定义的JDBC接口位于JDK的rt.jar中(java.sql包),因此这些接口会由BootstrapClassLoader进行加载;而数据库厂商提供的Driver驱动包一般由我们自己在应用程序中引入(比如位于CLASSPATH下),这已经超出了BootstrapClassLoader的加载范围,即这些驱动包中的JDBC接口的实现类无法被BootstrapClassLoader加载,只能由AppClassLoader或自定义的ClassLoader来加载。这样,SPI机制就没有办法实现。要解决这个问题,就需要使用Thread Context Class Loader。

下面就查看下JDK中的DriverManager类的源码,来看看其中Thread ContextClassLoader的使用。

public class DriverManager {

// 省略无关代码

static {

loadInitialDrivers(); // 在静态代码块中加载当前环境中的 JDBC Driver

println("JDBC DriverManager initialized");

}

private static void loadInitialDrivers() {

// 省略无关代码

AccessController.doPrivileged(new PrivilegedAction() {

public Void run() {

// 通过 ServiceLoader#load 方法来加载 Driver 的实现(如 MySQL、Oracle、PostgreSQL 提供的 Driver 实现)

// 即 SPI 机制

ServiceLoader loadedDrivers = ServiceLoader.load(Driver.class);

Iterator driversIterator = loadedDrivers.iterator();

try{

while(driversIterator.hasNext()) {

driversIterator.next();

}

} catch(Throwable t) {

// Do nothing

}

return null;

}

});

println("DriverManager.initialize: jdbc.drivers = " + drivers);

if (drivers == null || drivers.equals("")) {

return;

}

String[] driversList = drivers.split(":");

println("number of Drivers:" + driversList.length);

for (String aDriver : driversList) {

try {

println("DriverManager.Initialize: loading " + aDriver);

Class.forName(aDriver, true,

ClassLoader.getSystemClassLoader());

} catch (Exception ex) {

println("DriverManager.Initialize: load failed: " + ex);

}

}

}

}

DriverManager类在被加载的时候就会执行通过ServiceLoader#load方法来加载数据库驱动(即Driver接口的实现)。由于每个类都会使用加载自己的ClassLoader去加载其他的类(即它所依赖的类),因此可以简单考虑以上代码的类加载过程为:可以想一下,DriverManager类由BootstrapClassLoader加载,DriverManager类依赖于ServiceLoader类,因此BootstrapClassLoader也会尝试加载ServiceLoaer类,这是没有问题的;再往下,ServiceLoader的load方法中需要加载数据库(MySQL等)驱动包中Driver接口的实现类,即ServiceLoader类依赖这些驱动包中的类,此时如果是默认情况下,则还是由BootstrapClassLoader来加载这些类,但驱动包中的Driver接口的实现类是位于CLASSPATH下的,BootstrapClassLoader是无法加载的,这就有问题了。因此,在ServiceLoader#load方法中实际是指明了由ContextClassLoader来加载驱动包中的类:

public final class ServiceLoader implements Iterable {

// 省略无关代码

public static ServiceLoader load(Class service) {

// 需要注意的是,这里使用的是 当前线程的 ContextClassLoader 来加载实现,这也是 ContextClassLoader 为什么存在的原因。

ClassLoader cl = Thread.currentThread().getContextClassLoader();

return ServiceLoader.load(service, cl);

}

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值