深入理解JVM(七):线程上下文类加载器

每个类都会使用自己的类加载器(即加载自身的类加载)来去加载其它类(指的是所依赖的类)

如果ClassX引用了ClassY,那么ClassX的类加载器就回去加载ClassY(前提是Class尚未被加载)

线程上下文类加载(Context ClassLoader)

线程上下文类加载器是从JDK1.2开始引用的,getContextClassLoader()与setContextClassLoader(ClassLoader cl)

用来分别获取和设置上下文类加载器

如果没有通过setContextClassLoader(ClassLoader cl)进行设置的话,线程将继承其父线程的上下文类加载器.

java应用运行时的初始线程的上下文类加载器是系统类加载器.在线程中运行的代码可以通过该类加载器来加载类与资源

线程上下文类加载器的重要性:

父ClassLoader可以使用当前线程Thread.currentThread().getContextClassLoader()所指定的ClassLoader加载的类

这就改变了父ClassLoader不能使用子ClassLoader或其它没有直接父子关系的ClassLoader加载的类的情况,即改变了双亲委托模型

线程上下文类加载器就是当前线程的Current ClassLoader

SPI: service provider interface 服务提供者接口

双亲委托模型下,类加载是由上至下的,即下层的类加载器会委托上层进行加载.但是对于SPI来说,有些接口是JAVA核心库提供的

而JAVA核心库是由启动类加载器来加载的,而这些接口的实现却来自不同的jar包(厂商提供),Java的启动类加载器时不会加载其它来源的jar包

这样传统的双亲委托模型就无法满足SPI的要求,而通过给当前线程设置上下文类加载器,就可以由设置的上下文类加载器来实现对于接口实现类的加载

线程上下文类加载器的一般使用模式(获取,使用,还原)

//伪代码
ClassLoader classLoader =Thread.currentThread().getContextClassLoader();
try {
	Thread.currentThread().setContextClassLoader(targetTccl);
	myMethod();
} finally {
	Thread.currentThread().setContextClassLoader(classLoader);
}

myMethod()里面则调用了Thread.currentThread().getContextClassLoader(),获取当前线程的上下文类加载器做某些事情

如果一个类由类加载器A加载,那么这个类的依赖类也是由相同的类加载器加载的(如果该依赖类之前没有被加载过)

contextClassLoader的作用就是为了破坏java的类加载委托机制

当高层提供了统一的接口让底层区实现,同时又要在高层加载(或实例化底层的类时),就必须要通过线程上下文类加载器来帮助高层的ClassLoader找到并加载该类

public class MyTest26 {

    public static void main(String[] args) {

        //Thread.currentThread().setContextClassLoader(MyTest26.class.getClassLoader().getParent());

        ServiceLoader<Driver> loader =ServiceLoader.load(Driver.class);

        Iterator<Driver> iterator = loader.iterator();
        while(iterator.hasNext()){
            Driver driver = iterator.next();
            System.out.println("driver: "+driver.getClass()+" , loader: 				"+driver.getClass().getClassLoader());
        }

        System.out.println("当前线程上下文类加载器: "+Thread.currentThread().getContextClassLoader());
        System.out.println("ServiceLoader 的类加载器: " +ServiceLoader.class.getClassLoader());
    }
}

输出结果:

driver: class com.mysql.jdbc.Driver , loader: sun.misc.Launcher A p p C l a s s L o a d e r @ 18 b 4 a a c 2 d r i v e r : c l a s s c o m . m y s q l . f a b r i c . j d b c . F a b r i c M y S Q L D r i v e r , l o a d e r : s u n . m i s c . L a u n c h e r AppClassLoader@18b4aac2 driver: class com.mysql.fabric.jdbc.FabricMySQLDriver , loader: sun.misc.Launcher AppClassLoader@18b4aac2driver:classcom.mysql.fabric.jdbc.FabricMySQLDriver,loader:sun.misc.LauncherAppClassLoader@18b4aac2
当前线程上下文类加载器: sun.misc.Launcher$AppClassLoader@18b4aac2
ServiceLoader 的类加载器: null

结果分析:

先看一下ServiceLoader的doc文档:
在这里插入图片描述

首先定义了两个名词

service:一个众所周知的接口和抽象类的集合

service provider:这个集合的具体实现

例如:service是JDK定义的Driver接口,而service provider是各个数据库厂商驱动的具体实现

而service provider 需要提供一个无参的构造方法这样它们就可以在被加载的时候实例化,同时service provider还需要提供一个provider-configuration文件.这个文件被放置在资源路径META-INF/services目录下,文件的名字是service的全限定名.文件的内容是service provider的全限定名.

在这里插入图片描述

我们在看一下具体的源代码实现:首先,获取当前线程的上下文类加载器也就是系统类加载器

在这里插入图片描述

最终接口调用获取的就是META-INF路径下的配置

在这里插入图片描述

public class MyTest27 {

    public static void main(String[] args) throws ClassNotFoundException, SQLException {

        System.out.println(System.getProperty("jdbc.drivers"));
        Class.forName("com.mysql.jdbc.Driver");
        Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mytestdb","username","password");
    }
}

我们看一下jdbc的Class.forName(“com.mysql.jdbc.Driver”);到底做了什么事情

@CallerSensitive
public static Class<?> forName(String className)
            throws ClassNotFoundException {
    //获取调用者的Class对象,即MyTest27
    Class<?> caller = Reflection.getCallerClass(); 
    //使用调用者的类加载器加载com.mysql.jdbc.Driver并初始化
    return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}

在看一下DriverManager.getConnection(“jdbc:mysql://localhost:3306/mytestdb”,“username”,“password”);方法

@CallerSensitive
public static Connection getConnection(String url,
    String user, String password) throws SQLException {
    java.util.Properties info = new java.util.Properties();

    if (user != null) {
        info.put("user", user);
    }
    if (password != null) {
        info.put("password", password);
    }

    return (getConnection(url, info, Reflection.getCallerClass()));//获取调用者的Class对象即MyTest27
}
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");
}

看一下registeredDrivers是如何初始化的,根据静态代码块

static {
    loadInitialDrivers();
    println("JDBC DriverManager initialized");
}

看一下loadInitialDrivers();方法,首先获取系统属性jdbc.drivers,如果没有值则通过ServiceLoader.load(Driver.class)的方式加载,这个我们上述已经讲过了.所以现在再写JDBC,其实Class.forName(“com.mysql.jdbc.Driver”);是可以省略的.

在这里插入图片描述

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值