聊聊ClassLoader与jdbc的关系(contextClassLoader)

来源:互联网 发布:锋芒网络剧 编辑:程序博客网 时间:2024/05/22 08:10

背景

在前面聊到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()来避免双亲委托带来的尴尬;

原创粉丝点击