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


    为什么需要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<Void>() {
                public Void run() {
    
                    // 通过 ServiceLoader#load 方法来加载 Driver 的实现(如 MySQL、Oracle、PostgreSQL 提供的 Driver 实现)
                    // 即 SPI 机制
                    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;
                }
            });
    
            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<S> implements Iterable<S> {
    
        // 省略无关代码
    
        public static <S> ServiceLoader<S> load(Class<S> service) {
            // 需要注意的是,这里使用的是 当前线程的 ContextClassLoader 来加载实现,这也是 ContextClassLoader 为什么存在的原因。
            ClassLoader cl = Thread.currentThread().getContextClassLoader();
            return ServiceLoader.load(service, cl);
        }
        
    } 
    
    原创内容,未经允许,禁止任何网站及个人转载
  • 相关阅读:
    sql 索引创建
    sql 触发器
    sql 中延迟执行
    sql 存储过程 分页
    BETWEEN and
    sql case when 速记
    Set无序怎么办?
    TCP为什么需要3次握手与4次挥手
    定时器
    JAVA 类加载器 第14节
  • 原文地址:https://www.cnblogs.com/guiblog/p/14244064.html
Copyright © 2020-2023  润新知