聊聊ClassLoader与jdbc的关系(contextClassLoader)

背景

在前面聊到ClassLoader是如何工作的,有些时候ClassLoader的双亲委托机制不能完成一些特定的类加载任务,比如java提供一些SPI,由厂商来进行具体的实现,比如jdbc,各个数据库厂商根据java提供的SPI来实现各自数据库的连接;这些SPI都定义在核心类里,由bootstrap ClassLoader加载,而在SPI 接口中的代码经常需要加载具体的实现类,但厂商的具体实现又不能由bootstrap ClassLoader加载,那如何实现的呢?答案是contextClassLoader(线程上下文类加载器),下面来以jdbc为例看看到底是如何工作的

实现

先来看一小段jdbc获取连接的代码

Class.forName("com.mysql.jdbc.Driver"); 
DriverManager.getConnection("jdbc:mysql://localhost:3306/db", "user", "psw");

深挖一下上面的代码,首先Class.forName方法都知道是加载指定的类

  • 那么就再看看forName方法吧
public static Class<?> forName(String className) throws ClassNotFoundException {
    //通过Reflection.getCallerClass()方法获取到调用forName的class
    Class<?> caller = Reflection.getCallerClass();
    //调用forName0,传入的ClassLoader为加载caller的ClassLoader,forName0为native方法
    return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}
  • 再看看加载com.mysql.jdbc.Driver的时候会发生什么
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    public Driver() throws SQLException {
    }

    static {
        try {
            DriverManager.registerDriver(new Driver());
        } catch (SQLException var1) {
            throw new RuntimeException("Can\'t register driver!");
        }
    }
}

注意到这里有个静态代码块,而class的forName方法默认对加载的类进行了链接操作,所以这里的静态代码块会被执行;静态代码块将com.mysql.jdbc.Driver注册到DriverManager,其实就是被DriverManager的静态成员registeredDrivers持有缓存起来,而DriverManager也在这个时候被加载了,根据ClassLoader的双亲委托机制,DriverManager由bootstrap ClassLoader加载;到这里class的forName方法就执行完了

  • 下面执行getConnection(获取数据库连接)
public static Connection getConnection(String url,
        String user, String password) throws SQLException {
    //将用户名和密码封装到properties里
    java.util.Properties info = new java.util.Properties();
    if (user != null) {
        info.put("user", user);
    }
    if (password != null) {
        info.put("password", password);
    }
    //调用真正的获取连接的方法,指定caller的class为调用当前方法的类
    return (getConnection(url, info, Reflection.getCallerClass()));
}

private static Connection getConnection(
        String url, java.util.Properties info, Class<?> caller) throws SQLException {
    //如果caller存在,则将ClassLoader设置为加载caller的ClassLoader
    ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
    synchronized(DriverManager.class) {
        //如果ClassLoader不存在,那么ClassLoader设置为当前线程上下文的类加载器(getContextClassLoader)为ClassLoader
        if (callerCL == null) {
            callerCL = Thread.currentThread().getContextClassLoader();
        }
    }

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

    println("DriverManager.getConnection(\"" + url + "\")");
    SQLException reason = null;
    //循环注册了的Driver
    for(DriverInfo aDriver : registeredDrivers) {
        //如果callerClassLoader有权限加载aDriver.driver,即:判断两个driver是否由同一个ClassLoader加载的
        if(isDriverAllowed(aDriver.driver, callerCL)) {
            try {
                println("    trying " + aDriver.driver.getClass().getName());
                //通过之前注册的drive获取数据库连接
                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 (reason != null)    {
        println("getConnection failed: " + reason);
        throw reason;
    }
    throw new SQLException("No suitable driver found for "+ url, "08001");
}

小结

SPI加载实现类主要是通过Reflection.getCallerClass()和Thread.currentThread().getContextClassLoader()来避免双亲委托带来的尴尬;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值