您的位置:首页 > 其它

jdbc驱动的类加载过程

2017-11-03 14:21 399 查看
第一步,加载数据库的驱动

Class.forName("oracle.jdbc.driver.OracleDriver")

Class.forName("com.mysql.jdbc.Driver")

Class.forName 方法会根据类的全路径名称去加载对应的class文件,生成类型,并初始化类型。也就是说static语句块会执行。

下面来看看 com.mysql.jdbc.Driver 类

复制代码

 1 public class Driver extends NonRegisteringDriver implements java.sql.Driver {

 2     //

 3     // Register ourselves with the DriverManager

 4     //

 5     static {

 6         try {

 7             java.sql.DriverManager.registerDriver(new Driver());

 8         } catch (SQLException E) {

 9             throw new RuntimeException("Can't register driver!");

10         }

11     }

12 

13     /**

14      * Construct a new driver and register it with DriverManager

15      * 

16      * @throws SQLException

17      *             if a database error occurs.

18      */

19     public Driver() throws SQLException {

20         // Required for Class.forName().newInstance()

21     }

22 }

复制代码

里面的主要逻辑都在父类 NonRegisteringDriver 里实现,而static语句块就做了一件事:生成驱动实例,并向DriverManager注册。所谓注册,就是将driver的信息保存起来,以便后来取用。

 

第二步,取得数据库连接connection

 

Connection conn= DriverManager.getConnection(url, user, password);

 

这里为什么通过DriverManager来取,而不是直接通过生成driver来取???后面马上揭晓!!!

复制代码

 1 public static Connection getConnection(String url,

 2         String user, String password) throws SQLException {

 3         java.util.Properties info = new java.util.Properties();

 4 

 5         if (user != null) {

 6             info.put("user", user);

 7         }

 8         if (password != null) {

 9             info.put("password", password);

10         }

11 

12         return (getConnection(url, info, Reflection.getCallerClass()));

13     }

复制代码

Reflection.getCallerClass() 是取得调用类,这个方法是native的。我这里是jdk1.8,以前的版本不是调用的这个方法,如果感兴趣也可以看看。

复制代码

 1 private static Connection getConnection(

 2         String url, java.util.Properties info, Class<?> caller) throws SQLException {

 3         /*

 4          * When callerCl is null, we should check the application's

 5          * (which is invoking this class indirectly)

 6          * classloader, so that the JDBC driver class outside rt.jar

 7          * can be loaded from here.

 8          */

 9         ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;

10         synchronized(DriverManager.class) {

11             // synchronize loading of the correct classloader.

12             if (callerCL == null) {

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

14             }

15         }

16 

17         if(url == null) {

18             throw new SQLException("The url cannot be null", "08001");

19         }

20 

21         println("DriverManager.getConnection(\"" + url + "\")");

22 

23         // Walk through the loaded registeredDrivers attempting to make a connection.

24         // Remember the first exception that gets raised so we can reraise it.

25         SQLException reason = null;

26 

27         for(DriverInfo aDriver : registeredDrivers) {

28             // If the caller does not have permission to load the driver then

29             // skip it.

30             if(isDriverAllowed(aDriver.driver, callerCL)) {

31                 try {

32                     println("    trying " + aDriver.driver.getClass().getName());

33                     Connection con = aDriver.driver.connect(url, info);

34                     if (con != null) {

35                         // Success!

36                         println("getConnection returning " + aDriver.driver.getClass().getName());

37                         return (con);

38                     }

39                 } catch (SQLException ex) {

40                     if (reason == null) {

41                         reason = ex;

42                     }

43                 }

44 

45             } else {

46                 println("    skipping: " + aDriver.getClass().getName());

47             }

48 

49         }

50 

51         // if we got here nobody could connect.

52         if (reason != null)    {

53             println("getConnection failed: " + reason);

54             throw reason;

55         }

56 

57         println("getConnection: no suitable driver found for "+ url);

58         throw new SQLException("No suitable driver found for "+ url, "08001");

59     }

复制代码

这个方法一开始就要得到调用类caller的类加载器callerCL,为的是后面再去加载数据库的driver,做一下验证,

具体在 isDriverAllowed(aDriver.driver, callerCL) 里面的代码里。

那问题来了,为什么就不能用加载DriverManager的类加载器呢???

因为DriverManager在rt.jar里面,它的类加载器上启动类加载器。而数据库的driver(com.mysql.jdbc.Driver)是放在classpath里面的,启动类加载器是不能加载的。所以,如果严格按照双亲委派模型,是没办法解决的。而这里的解决办法是:通过调用类的类加载器去加载。而如果调用类的加载器是null,就设置为线程的上下文类加载器:

Thread.currentThread().getContextClassLoader()

好的,下面通过Thread类的源码,分析线程的上下文类加载器。

 

复制代码

    /* The context ClassLoader for this thread */

    private ClassLoader contextClassLoader;

    // 这段if---else代码出自init方法

    if (security == null || isCCLOverridden(parent.getClass()))

            this.contextClassLoader = parent.getContextClassLoader();

    else

            this.contextClassLoader = parent.contextClassLoader;

    public ClassLoader getContextClassLoader() {

        if (contextClassLoader == null)

            return null;

        SecurityManager sm = System.getSecurityManager();

        if (sm != null) {

            ClassLoader.checkClassLoaderPermission(contextClassLoader,

                                                   Reflection.getCallerClass());

        }

        return contextClassLoader;

    }

    public void setContextClassLoader(ClassLoader cl) {

        SecurityManager sm = System.getSecurityManager();

        if (sm != null) {

            sm.checkPermission(new RuntimePermission("setContextClassLoader"));

        }

        contextClassLoader = cl;

    }

复制代码

 

 

init方法里面代码的逻辑是:把父线程的上下文类加载器给继承过来。这里的父子关系是指谁启动谁的关系,比如在线程A里面启动了线程B,那B线程的父线程就是A。

既然都是一路继承,那第一个启动的线程(包含main方法的那个线程)里面的contextClassLoader是谁设置的呢???

这就要看 sun.misc.Launcher 这个类的源码。Launcher是JRE中用于启动程序入口main()的类。

loader = AppClassLoader.getAppClassLoader(extcl);

Thread.currentThread().setContextClassLoader(loader);

这里截取的两行代码出自 Launcher 的构造方法。第一行用一个扩展类加载器extcl构造了一个系统类加载器loader,第二行把loader设置为当前线程(包含main方法)的类加载器。所以,我们启动一个线程的时候,如果之前都没有调用 setContextClassLoader 方法明确指定的话,默认的就是系统类加载器。

到这里,整个加载流程基本上一目了然了。

 

现在,再回到之前 DriverManager的getConnection 方法,好像还有一个疑问没有解决。

复制代码

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());

            }

        }

复制代码

最后返回一个connection,但是在一个循环里面。也就是说,当我们注册了多个数据库驱动,mysql,oracle等;DriverManager都帮我们管理了,它会取出一个符合条件的driver,就不用我们在程序里自己去控制了
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: