JDBC Driver加载时是怎么破坏双亲委派的、它是怎么实现的

2019-08-28  本文已影响0人  Easy的幸福

一、JDBC之前加载驱动的方式

在说破坏双亲委派之前,先看下之前是怎么加载Driver的。在刚开始的时候JDBC在加载class的时候,其实是直接利用了Class.classforName

Class.forName()和ClassLoader.loadClass区别
Class.forName(className)方法,内部实际调用的方法是  Class.forName(className,true,classloader);

第2个boolean参数表示类是否需要初始化,  Class.forName(className)默认是需要初始化。

一旦初始化,就会触发目标对象的 static块代码执行,static参数也也会被再次初始化。

    

ClassLoader.loadClass(className)方法,内部实际调用的方法是  ClassLoader.loadClass(className,false);

第2个 boolean参数,表示目标对象是否进行链接,false表示不进行链接,由上面介绍可以,

不进行链接意味着不进行包括初始化等一些列步骤,那么静态块和静态对象就不会得到执行
//加载Oracle数据库驱动 
Driver driver = (Driver)Class.forName("oracle.jdbc.driver.OracleDriver"); 
   
//加载SQL Server数据库驱动 
Driver driver = (Driver)Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver"); 
   
//加载MySQL 数据库驱动 
Driver driver = (Driver)Class.forName("com.mysql.jdbc.Driver");

Class.forName()将对应的驱动类加载到内存中,然后执行内存中的static静态代码段,代码段中,会创建一个驱动Driver的实例,放入DriverManager中,供DriverManager使用。

static { 
    Timestamp localTimestamp = Timestamp.valueOf("2000-01-01 00:00:00.0"); 
    try { 
        if (defaultDriver == null) { 
            //创建一个OracleDriver实例,然后注册到DriverManager中 
            defaultDriver = new OracleDriver(); 
            DriverManager.registerDriver(defaultDriver); 
        } 
 
    } catch (RuntimeException localRuntimeException) { 
    } catch (SQLException localSQLException) { 
 }

下面是一个加载Driver、注册、注销、重新注册的代码示例:

public static void defaultDriver(){ 
    try { 
           
        String url = "jdbc:oracle:thin:@127.0.0.1:1521:xe"; 
           
        //1.将Driver加载到内存中,然后执行其static静态代码,创建一个OracleDriver实例注册到DriverManager中 
        Driver dd = (Driver)Class.forName("oracle.jdbc.driver.OracleDriver").newInstance(); 
        //2.取出对应的oracle 驱动Driver 
        Driver driver  = DriverManager.getDriver(url); 
        System.out.println("加载类后,获取Driver对象:"+driver); 
           
        //3. 将driver从DriverManager中注销掉 
        DriverManager.deregisterDriver(driver); 
           
        //4.此时DriverManager中已经没有了驱动Driver实例,将创建的dd注册到DriverManager中 
        DriverManager.registerDriver(dd); 
           
        //5.重新通过url从DriverManager中取Driver 
        driver  = DriverManager.getDriver(url); 
                         
        System.out.println("注销掉静态创建的Driver后,重新注册的Driver:    "+driver); 
        System.out.println("driver和dd是否是同一对象:" +(driver==dd)); 
    } catch (Exception e) { 
        System.out.println("加载Oracle类失败!"); 
        e.printStackTrace(); 
    } finally{ 
           
    } 
} 

以上其实就是早期我们加载JDBC时候的方式。但是这种从上面的代码上也可以看出,假如说我要加载不同的Driver,那么我要去加载不同的
url,来实例化不同的Driver,这样的话会造成代码的冗余,或者说代码之间的耦合性太强(代码要使用的Driver和和Driver的实现耦合到一起),
还有就是如上面写的类似于“oracle.jdbc.driver.OracleDriver”这样的一堆的字符串,所以,之后就出现了利用破坏双亲委派这种机制来加载Driver的方式。

二、JDBC现在加载驱动的方式

接下来看java是如何破坏双亲委派来加载 Driver的,看源码,首先要一个入口:

public static void main(String[] args) throws SQLException {
        String url = "jdbc:mysql://127.0.0.1:3306/test?characterEncoding=utf-8&useSSL=false";
        String username = "root";
        String password = "Ys0120.";
        Connection conn = null;
        try {
            conn = DriverManager.getConnection(url, username, password);
            PreparedStatement statement = conn.prepareStatement("select * from user where id = ?");
            statement.setLong(1,1);
            ResultSet set = statement.executeQuery();
            while(set.next()){
                System.out.println(set.getString("name"));
            }
            conn.close();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            if(conn!=null){
                try {
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }

代码在执行DriverManager.getConnection(url, username, password); 首先回去实例化DriverManager,而DriverManager是整个加载的核心
DriverManager里面有一个静态的代码块,在实例化的时候会进行初始化执行。

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


loadInitialDrivers();整个方法里面最核心也是执行加载的核心的两行代码如下:


 ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
                Iterator<Driver> driversIterator = loadedDrivers.iterator();

下面的load方法重点在于ClassLoader cl = Thread.currentThread().getContextClassLoader();

线程上下文件类加载器(Thread Context ClassLoader)。这个类加载器可以通过java.lang.Thread类的setContextClassLoader()方法进行设置,如果创建线程时还未设置,它将会从父线程中继承一个;如果在应用程序的全局范围内都没有设置过,那么这个类加载器默认就是应用程序类加载器。

public static <S> ServiceLoader<S> load(Class<S> service) {

        //因为出入的service 是  java.sql.Driver,它是一个接口,jdk支持了 spi 这种提供接口的方式
        //供调用者去实现它的接口,具体调用者去调用哪个实现,Driver不管。
        //因为当前service  是一个第三方的类或者说非java自己的类,所以JVM 是无法调用自己的
        //类加载器去加载这个Driver类的,所以它需要一个appClassLoader去加载,所以它用了
        //TCCL去获取一个 aapClassLoader,然后去执行实例化。
        // spi 指的是(service  provide interface),第三方可以以jar 的形式提供一个接口类,
        //调用者 可以根据自己的业务去实现。
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        return ServiceLoader.load(service, cl);
    }

下面是具体的实例化的代码,它的底层其实是用了Class.forName去进行实例化。至此,java在加载Driver
的时候是怎么去破坏双亲模型的给说完了,剩下的代码基本上不属于破坏双亲模型的范畴。

 private S nextService() {
            if (!hasNextService())
                throw new NoSuchElementException();
            String cn = nextName;
            nextName = null;
            Class<?> c = null;
            try {
                c = Class.forName(cn, false, loader);
            } catch (ClassNotFoundException x) {
                fail(service,
                     "Provider " + cn + " not found");
            }
            if (!service.isAssignableFrom(c)) {
                fail(service,
                     "Provider " + cn  + " not a subtype");
            }
            try {
                S p = service.cast(c.newInstance());
                providers.put(cn, p);
                return p;
            } catch (Throwable x) {
                fail(service,
                     "Provider " + cn + " could not be instantiated",
                     x);
            }
            throw new Error();          // This cannot happen
        }
上一篇下一篇

猜你喜欢

热点阅读