JAVA(3)- 线程上下文类加载器

本文介绍了Java线程上下文类加载器的作用,解释了为何需要它来解决SPI(ServiceProviderInterface)加载问题。通过数据库驱动初始化源码分析,揭示了如何打破双亲委托模型,使用Thread.currentThread().getContextClassLoader()来加载第三方类。文章总结了这种由子类加载器加载的策略在现代开源项目中的常见应用。
摘要由CSDN通过智能技术生成


前言

线程上下文类加载器
扩展:

概念

SPI:Service Provider Interface 、由JDK定义的一套标准接口,方便扩展各个厂商的不同实现

1、为什么需要线程上下文类加载器

  • JDK核心库中提供了很多SPI(Service Provider Interface),常见的SPI包括JDBC、JCE、JNDI、JAXP、和JBI等、JDK只规定了这些接口之间的逻辑关系,但不提供具体的实现,具体的实现由第三方厂商提供,这样做的好处是JDBC提供了高度抽象,应用程序只需要面向接口编程。
  • 问题:java.lang.sql中的所有接口都由JDK提供、加载这些类的是根类加载器,第三方提供的是系统应用类加载器,由于JVM的双亲委托机制,比如Connections、Statement、RowSet等都是由根类加载器加载、第三方的类不会被加载,那是如何解决这个问题的呢?

2、数据库驱动初始化源码分析

  • rt.jar包 DriverManager 是BootstrapClassLoader加载
  • 父类加载器委托子类类加载器去加载 破坏双亲委派模型
callerCL = Thread.currentThread().getContextClassLoader();

aClass =  Class.forName(driver.getClass().getName(), true, classLoader);
//  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();
            }
        }

        if(url == null) {
            throw new SQLException("The url cannot be null", "08001");
        }

        println("DriverManager.getConnection(\"" + url + "\")");

        // 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.
            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);
                    }
                } catch (SQLException ex) {
                    if (reason == null) {
                        reason = ex;
                    }
                }

            } else {
                println("    skipping: " + aDriver.getClass().getName());
            }

        }

        // if we got here nobody could connect.
        if (reason != null)    {
            println("getConnection failed: " + reason);
            throw reason;
        }

        println("getConnection: no suitable driver found for "+ url);
        throw new SQLException("No suitable driver found for "+ url, "08001");
    }



private static boolean isDriverAllowed(Driver driver, ClassLoader classLoader) {
        boolean result = false;
        if(driver != null) {
            Class<?> aClass = null;
            try {
                aClass =  Class.forName(driver.getClass().getName(), true, classLoader);
            } catch (Exception ex) {
                result = false;
            }

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

        return result;
    }

3、总结

  • 在Thread中设置以下两个方法就是为了解决SPI的问题
Thread.currentThread().getContextClassLoader()
Thread.currentThread().setContextClassLoader();
  • 由父委托变为子委托的方式,打破了双亲委托机制的模型,几乎所有涉及SPI加载的动作采用的都是这种方式。
  • 现在开源社区会经常遇到标准的接口和第三方实现独立设计的情况,比如slf4j只是log的标准接口库,而slf4j-log4j则是其中的一个实现,在真实项目中,两者皆由同一个类加载器进行加载。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值