线程上下文类加载器

线程上下文类加载器_第1张图片

前言

看本文之前,推荐一篇博客:Class.forName 和 ClassLoader 到底有啥区别?

为什么要有线程上下文类加载器

根绝Thread类的文档你会发现线程上下文方法是JDK1.2开始引入的,setContextClassLoader()和setContextClassLoader()分别用于获取设置当前线程上下文类加载器,如果当前线程没有设置上下文类加载器,那么它将和父线程保持同样的类加载器。

为什么要有线程上下文类加载器呢?这就与JVM类加载器双亲委托机制自身的缺陷是分不开的,JDK和核心类库提供了很多SPI,常见的SPI包括JDBC、JNDI等,JDK只规定了这些接口之间的逻辑关系,但是不提供具体实现,具体实现由第三方厂商来提供。

这样做的好处是JDBC提供了高度的抽象,应用程序则只需要面向接口编程即可,不用关心各大数据库厂商提供的实现,但是问题在于java.lang.sql中的所有接口都由JDK提供,加载这些接口的类加载器是根加载器,第三方厂商提供的类驱动则是由系统类加载器加载的,由于双亲委托机制,比如Connections、Statement、RowSet等都是跟加载器加载的,第三方的JDBC驱动包中的实现不会被加载,那么又是如何解决这个问题呢?

数据库驱动源码分析

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    //
    // Register ourselves with the DriverManager
    //
    static {
        try {
            java.sql.DriverManager.registerDriver(new Driver());
        } catch (SQLException E) {
            throw new RuntimeException("Can't register driver!");
        }
    }

    /**
     * Construct a new driver and register it with DriverManager
     * 
     * @throws SQLException
     *             if a database error occurs.
     */
    public Driver() throws SQLException {
        // Required for Class.forName().newInstance()
    }
}

Driver类的静态代码主要是讲Mysql的Driver实例注册给了DriverManager,因此直接使用DriverManger.registerDriver作用和Class.forName是完全等价的。

底层具体实现:registerDriver()将driver实例注册到java.sql.DriverManager类中,其实就是将com.mysql.jdbc.Driver添加到java.sql.DriverManager类的静态变量CopyOnWriteArrayList集合中。

看下java.sql.DriverManager这个类

 static {
    //初始化Driver驱动实现类:
    loadInitialDrivers();
    println("JDBC DriverManager initialized");
}

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;
    }
    //通过spi来加载jdbc驱动实现类:
    AccessController.doPrivileged(new PrivilegedAction<Void>() {
        public Void run() {
            //通过SPI方式,读取META-INF/services下文件中的类:
            ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
            Iterator driversIterator = loadedDrivers.iterator();
            try{
                while(driversIterator.hasNext()) {
                    driversIterator.next();
                }
            } catch(Throwable t) {}
            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上有两种方式:

  • 调用Class.forName方法
  • DriverMannager自身也会通过spi的方式遍历出所有META-INF/services文件夹下的实现类名称

然后我们再看ServiceLoader.load(Driver.class)这个具体方法

public static <S> ServiceLoader<S> load(Class<S> service) {
    //获取线程上下文类加载器:
    ClassLoader cl = Thread.currentThread().getContextClassLoader();
    //生成ServiceLoader对象:
    return ServiceLoader.load(service, cl);
}

public static <S> ServiceLoader<S> load(Class<S> service,
                                        ClassLoader loader){
    return new ServiceLoader<>(service, loader);
}
    
private ServiceLoader(Class<S> svc, ClassLoader cl) {
    service = svc;
    loader = cl;
    reload();
}

在获取ServiceLoader对象时,获取了此时线程上下文中的类加载器,将此类加载赋值给ServiceLoader类中的loader成员变量。在后续类加载过程中,都是使用的此类加载来完成。这一步的操作,直接打破了双亲委派模型,实现了逆向类加载。

ContextClassLoader默认存放了AppClassLoader的引用,由于它是在运行时被放在了线程中,在任何需要的时候都可以用Thread.currentThread().getContextClassLoader()取出应用程序类加载器来完成需要的操作。

总结

只要你遵循约定(把类名写在/META-INF里),那当我启动时我会去扫描所有jar包里符合约定的类名,再调用forName加载,但我的ClassLoader是没法加载的,那就把它加载到当前执行线程上下文类加载器里,后续你想怎么操作(驱动实现类的static代码块)就是你的事了。

你可能感兴趣的:(多线程,spring,java,jdbc)