线程上下文类加载器

为什么需要线程上下文类加载器
根据Thread类的文档你会发现线程上下文方法是从JDK1.2开始引入的,getContextClassLoader()和
setContextClassloader(ClassLoader cl)分别用于获取和设置当前线程的上下文类加载器,如果当前线程
没有设置上下文类加载器,那么它将和父线程保持ton光的类加载器。站在开发者的角度,其他线程都是由Main
线程,也就是main函数所在的线程派生的,它是其他线程的父线程或者祖先线程。下面进行线程上下文类的简单
测试,
代码如下:

import static java.lang.Thread.currentThread;

/**
* @author xxx xshlxx@126.com
* @since 2022/7/12
*/
public class MainThreadClassLoader {

  public static void main(String[] args) {
      System.out.println(currentThread().getContextClassLoader());
  }
}

sun.misc.Launcher$AppClassLoader@14dad5dc
为什么要有线程上下文类加载器呢?<font color = "red">这就与JVM类加载器双亲委派机制自身的缺陷是分不
开的</font>JDK的核心库中提供了很多SPI(Service Provider Interface),常见的SPI包括JDBC、JCE、
JNDI和JBI等,JDK之规定了这些接口之间的逻辑关系,但不提供具体的实现,具体的实现需要由第三方厂商来提
供,作为java程序员或多或少地都写过JDBC程序,在编写JDBC程序时几乎百分百都在与java.sql包下的类打交
道。 

如图,java使用JDBC这个SPI完全透明了应用程序和第三方厂商的数据驱动的具体实现,不管数据类型如何切换,
应用程序只需要替换JDBC的驱动jar包以及数据库的驱动名称即可,而不用进行任何更新。

j2NsfS.md.png

这样做的好处是JDBC提供了高度抽象,应用程序则只需要面向接口编程即可,不用关心各大数据库厂商提供的具体
实现,<font color = "red">但问题在于java.lang.sql中的所有接口都由JDK提供,加载这些接口的类加载
器是根类加载器,第三方厂商提供的类库驱动则是由系统类加载器加载的</font>,由于JVM类加载器的双亲委派机
制,比如Connection、Statement、RowSet等皆由根加载器加载,第三方的JDBC驱动包中的实现不会被加载,
那么又如何解决这个问题呢?
数据库驱动的初始化源码分析
在编写所有的JDBC程序时,首先都需要调用Class.forName("xxx.xxx.xxx.Driver")对数据库驱动进行加
载,打开MySQL驱动Driver源码,代码如下

   static {
       try {
           // 注释1 在Mysql的静态方法中将Driver实例注册到DriverManager中
           DriverManager.registerDriver(new JDBC());
       }
       catch (SQLException e) {
           e.printStackTrace();
       }
   }
Driver类的静态代码块主要是将MySQL的Driver实例注册给DriverManager,因此直接使用DriverManager
.registerDriver(new com.mysql.jdbc.Driver())其作用与Class.forName
("xxx.xxx.xxx.Driver")是完全等价的。
下面我们继续看DriverManager的源码,毕竟数据库的链接就是从它而来的,代码如下:
    //  Worker method called by the public getConnection() methods.
    private static Connection getConnection(
        String url, java.util.Properties info, Class<?> caller) throws SQLException {
        /*
         * When callerCl is null, we should check the application's
         * (which is invoking this class indirectly)
         * classloader, so that the JDBC driver class outside rt.jar
         * can be loaded from here.
         */
        ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
        synchronized(DriverManager.class) {
            // synchronize loading of the correct classloader.
            if (callerCL == null) {
                callerCL = Thread.currentThread().getContextClassLoader();
            }
        }
        
  这里面说的很清楚了JDBC driver 除了可以在rt.jar加载外还可以在这里加载
  
  
          // Walk through the loaded registeredDrivers attempting to make a connection.
        // Remember the first exception that gets raised so we can reraise it.
        SQLException reason = null;

        for(DriverInfo aDriver : registeredDrivers) {
            // If the caller does not have permission to load the driver then
            // skip it.
            // 注释2
            if(isDriverAllowed(aDriver.driver, callerCL)) {
                try {
                    println("    trying " + aDriver.driver.getClass().getName());
                    Connection con = aDriver.driver.connect(url, info);
                    if (con != null) {
                        // Success!
                        println("getConnection returning " + aDriver.driver.getClass().getName());
                        return (con);
                    }
......
返回一个链接
......
    private static boolean isDriverAllowed(Driver driver, ClassLoader classLoader) {
        boolean result = false;
        if(driver != null) {
            Class<?> aClass = null;
            try {
                // 注释3
                aClass =  Class.forName(driver.getClass().getName(), true, classLoader);
            } catch (Exception ex) {
                result = false;
            }

             result = ( aClass == driver.getClass() ) ? true : false;
        }

        return result;
    }

上面的代码片段我只截取了部分,主要是用于说明与线程上下文加载器有关的内容。在注释1处获取当前线程的上下
文类加载器,该类就是调用Class.forName("X")所在线程上下文类加载器,通常是系统类加载器。

注释2中通过递归DriverManager中已经注册的驱动类,然后验证该数据库驱动是否可以被指定的类加载器加载
(线程上下文类加载器),如果颜很正通过则返回Connection,此刻返回的Connection则是数据库厂商提供的实例

注释3中的关键地方在于Class.forName(driver.getClass().getName(),true,classLoader);其使用线
程上下文类加载器进行数据库驱动的记载以及初始化

下面我们就来回顾下数据库驱动加载的整个过程,由于JDK定义了SPI的标准接口,加之这些接口被作为JDK核心类
库的一部分,既想完全透明标准接口的实现,又想与JDK核心库进行捆绑,由于JVM类加载双亲委托机制的限制,启
动类加载器不可能加载得到第三方厂商提供的具体实现。为了解决这个困境,JDK只好提供了一种不太优雅的设计--
-线程上下文类加载器,有了线程上下文类加载器,启动类加载器(根类加载器)反倒需要委托子类加载器去加载厂商
提供的SPI具体实现。

父委托变成了子委托的方式,这也打破了双亲委派机制的模型,而且由JDK官方亲自打破的
引用: Java高并发编程详解第11章
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值