jdbc之DriverManager分析

1、本文只是简单的描述本人看源码的一些记录,觉得比较重要的记录下来,如果有漏记重点,望大神们告之,万分感谢。

先扯点别的:

不知道各位有没有这种感觉,工作几年后,感觉技术达到一种瓶颈,在开发时,大多只停留在使用阶段,没有深究其框架的内部实现,使用都会,但一到优化就蒙逼了。

记不得在哪个公众号里看到“熟练工”一词了,可能上述就是吧,想让自己技术有提升,却不知道如何去做,现在我决定从jdk的源码入手,一点点去深入,虽不知这方法是否可行,至少行动了,每日看一点。本文也就在这样的背景下出现了。

欢迎大家来指点、交流如何提升技术。我会尽量每天写一点,分享自己的收获。

最后再插一句,如果发现文中有错误,请留言,感谢大家。

开始:

一个简单获取连接,只是为了找到入口,由入口开始分析。

Class.forName("com.mysql.jdbc.Driver");
connection = DriverManager.getConnection(
                    "jdbc:mysql://localhost:3306/microservicedb?useUnicode=true&characterEncoding=utf8", "root", "123456");

 这是关键的获取连接的地方。(因篇幅问题,删除了一些代码,只保留重要部分)

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

        for(DriverInfo aDriver : registeredDrivers) {
            if(isDriverAllowed(aDriver.driver, callerCL)) {
                try {
                    Connection con = aDriver.driver.connect(url, info);
                    if (con != null) {
                        // Success!
                        return (con);
                    }
                } catch (SQLException ex) {
                    if (reason == null) {
                        reason = ex;
                    }
                }
            }
        }
    }

上述代码的重点应该是两处

1、获取类加载器

2、registeredDrivers属性。

首先registeredDrivers是个静态属性,而getConnection(String, String, String)的方法也是静态方法,那么registeredDrivers的初始化,应该是由静态块加载(因为我们在拿连接时,并没有先创建DriverManager类,也不需要创建)。

果然(其实此处是为了加载由系统变量设置的驱动的:System.setProperty("jdbc.drivers","xxxxxx.Driver") 在第一次使用DriverManager之前设定才有效:

static {
        loadInitialDrivers();
        println("JDBC DriverManager initialized");
    }
private static void loadInitialDrivers() {
        // 第1部分
        String drivers;
        try {
            drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
                public String run() {
                    return System.getProperty("jdbc.drivers");
                }
            });
        } catch (Exception ex) {
            drivers = null;
        }
        // If the driver is packaged as a Service Provider, load it.
        // Get all the drivers through the classloader
        // exposed as a java.sql.Driver.class service.
        // ServiceLoader.load() replaces the sun.misc.Providers()
        
        //第二部分
        AccessController.doPrivileged(new PrivilegedAction<Void>() {
            public Void run() {

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

                /* Load these drivers, so that they can be instantiated.
                 * It may be the case that the driver class may not be there
                 * i.e. there may be a packaged driver with the service class
                 * as implementation of java.sql.Driver but the actual class
                 * may be missing. In that case a java.util.ServiceConfigurationError
                 * will be thrown at runtime by the VM trying to locate
                 * and load the service.
                 *
                 * Adding a try catch block to catch those runtime errors
                 * if driver not available in classpath but it's
                 * packaged as service and that service is there in classpath.
                 */
                try{
                    while(driversIterator.hasNext()) {
                        driversIterator.next();
                    }
                } catch(Throwable t) {
                // Do nothing
                }
                return null;
            }
        });
        // 第三部分
        println("DriverManager.initialize: jdbc.drivers = " + drivers);

        if (drivers == null || drivers.equals("")) {
            return;
        }
        String[] driversList = drivers.split(":");
        println("number of Drivers:" + driversList.length);
        for (String aDriver : driversList) {
            try {
                println("DriverManager.Initialize: loading " + aDriver);
                Class.forName(aDriver, true,
                        ClassLoader.getSystemClassLoader());
            } catch (Exception ex) {
                println("DriverManager.Initialize: load failed: " + ex);
            }
        }
    }

loadInitialDrivers()方法中有两个少用(在自己写功能的时候)的点:

1、AccessController.doPrivileged方法

大家可以去找答案。

有篇简单介绍的文章https://www.cnblogs.com/langtianya/p/5133633.html 

2、关于ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);

其实时从META-INF文件夹中读取现实Driver接口的实现类的,在这里,其实就是读取mysql-connection-java的META-INF/services/java.sql.Driver 文件。此处的java.sql.Driver文件名是必要的,不能写成别的。就是接口的全路径。还不清楚的自己去查找相关ServiceLoader的资料吧。里面的内容有

com.mysql.jdbc.Driver
com.mysql.fabric.jdbc.FabricMySQLDriver

正式说下loadInitialDrivers方法:

我将上述的代码分成了三个部分。

第一部分:主要是加载系统变量的System.setProperty("jdbc.drivers","xxxxxx.Driver") 多个之间用冒号分割(注意:必须在第一次使用DriverManager的时候设置才有效,static块只会加载一次)。

第三部分是对第一部分的延续,将读取到驱动初始化(在static块中调用 DriverManager.registerDriver)。

其实也是调用Class.forName(aDriver, true,ClassLoader.getSystemClassLoader()); 来做的。

第二部分(有这个,其实在我们做入口的时候不用再调用Class.forName("com.mysql.jdbc.Driver");了):

通过ServiceLoadr可以获取到Driver的实现类(还不清楚怎么获取到的童鞋自己查查ServiceLoadr)

然后调用driversIterator.next();  这个调用其实是调用了ServiceLoadr中的nextService();

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
        }

上述代码,最主要的部分是{c = Class.forName(cn, false, loader); }  大括号的代码其实并不会初始化(即不会调用static块,因为initial是false)而是由下面的service.cast(c.newInstance()) 来创建实例时调用的static块。 此处做个补充。

也就是说,其实driversIterator.next();中已经帮我们做了Class.forName了,也就是做了注册Driver的工作了。

别被这里的Iterator的next给迷惑了。

 

我们看看com.mysql.jdbc.Driver类初始化时做了什么:

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

没错,就是调用了注册的方法。再次回到getConnection方法。

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

此处的aDriver.driver就获取到了我们需要的com.mysql.jdbc.Driver对象。

aDriver.driver.connect(url, info)该方法的实现其实是在com.mysql.jdbc.Driver的父类com.mysql.jdbc.NonRegisteringDriver中的。

-----------------------------------------转发请注明出处。--------------------------------------------------------------------------------

DriverManager的其它方法下次写。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值