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

2021-06-10 21:05

阅读:877

标签:final   private   设置   调用   jvm   ora   输出   mysql   载器   

为什么需要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);
    }
    
} 

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

标签:final   private   设置   调用   jvm   ora   输出   mysql   载器   

原文地址:https://www.cnblogs.com/guiblog/p/14244064.html


评论


亲,登录后才可以留言!