双亲委派模型的破坏(JDBC例子)

双亲委派模型的破坏

双亲委托模型并不是一个强制性的约束模型,而是java设计者推荐给开发者的泪加载器实现方式。但是双亲委托模型存在着缺陷,它虽然解决了各个类加载器的基础类的统一问题,基础被称为基础,就是因为他们总是被用户代码调用,但是如果基础类又要调用回用户代码呢?那么在就会使用基础类的类加载器(启动类加载器)去加载用户的代码,而启动类加载器是加载java_home\lib目录下的。而用户代码都是保存在classpath下,根本就不可能加载到啊=。=其中这个方面最典型的就是jdbc对于双亲委派模型的破坏了。

JDBC对双亲委派模型的破坏

先来看一段代码的描述:

public class JdbcTest {
    public static void main(String[] args){
        Connection connection = null;
        try {
            connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/test", "root", "awakeyo");
        } catch (SQLException e) {
            e.printStackTrace();
        }
        System.out.println(connection.getClass().getClassLoader());
        System.out.println(Thread.currentThread().getContextClassLoader());
        System.out.println(Connection.class.getClassLoader());
    }

}

输出结果:

双亲委派模型的破坏(JDBC例子)_第1张图片

很明显我们发现Connection类的类加载器是启动类加载器(应用类类加载器委托给启动类加载器、双清委托模型),因为它输出的是null,而第一第二行输出语句输出的是appClassloader应用程序类加载器.

因此,我们证明了java.sql.Connection是委托给应用加载器加载,但其子类的getConnection却可以加载不在lib下的类???。根据类加载机制,当被装载的类引用了另外一个类的时候,虚拟机就会使用装载该类的类装载器装载被引用的类,这不就是要用应用类加载器加载classpath下的包???这,难为了吧,这违反了根据其定义的委托模型。那么他是如何做到的呢?

原因在于:getConnection方法

private static Connection getConnection(
        String url, java.util.Properties info, Class<?> caller) throws SQLException {
        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 + "\")");
        SQLException reason = null;

        for(DriverInfo aDriver : registeredDrivers) {
          		//isDriverAllowed对于mysql连接jar进行加载
            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 (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");
    }

获取线程上下为类加载器

callerCL = Thread.currentThread().getContextClassLoader();

isDriverAllowed对于mysql连接jar进行加载

isDriverAllowed(aDriver.driver, callerCL))

isDriverAllowed将传入的Thread.currentThread().getContextClassLoader();拿到的应用类加载器用去Class.forName加载我们mysql连接jar,这样子就可以加载到我们自己的mtsql连接jar

    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;
    }

为什么必须要破坏?

DriverManager::getConnection 方法需要根据参数传进来的 url 从所有已经加载过的 Drivers 里找到一个合适的 Driver 实现类去连接数据库.
Driver 实现类在第三方 jar 里, 要用 AppClassLoader 加载. 而 DriverManager 是 rt.jar 里的类, 被 BootstrapClassLoader 加载, DriverManager 没法用 BootstrapClassLoader 去加载 Driver 实现类(不再lib下), 所以只能破坏双亲委派模型, 用它下级的 AppClassLoader 去加载 Driver.

你可能感兴趣的:(jvm)