前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >ServiceLoader和DriverManager的前世今生

ServiceLoader和DriverManager的前世今生

作者头像
大忽悠爱学习
发布2022-06-06 09:33:53
7070
发布2022-06-06 09:33:53
举报
文章被收录于专栏:c++与qt学习

ServiceLoader和DriverManager的前世今身


JDBC获取数据库连接的方式

我们先来看看JDBC获取数据库连接有哪几种做法:

  • 直接实例化出特定的驱动,不经过DriverManager,这样的好处是方便,坏处是需要提前知道存在哪些驱动可以使用,因此该方式不推荐。
代码语言:javascript
复制
        Driver driver=new com.mysql.jdbc.Driver();
        String url="jdbc:mysql://localhost:3306/test1";
        Properties info=new Properties();
        info.setProperty("user","root");
        info.setProperty("password","xxx");
        Connection conn=driver.connect(url,info);
  • 和第一种方式没有区别,这里不再多说
代码语言:javascript
复制
       Class clazz=Class.forName("com.mysql.jdbc.Driver");
       Driver driver=(Driver)clazz.newInstance();
       String url="jdbc:mysql://localhost:3306/test1";
        Properties   info=new Properties();
        info.setProperty("user","root");
        info.setProperty("password","xxx");
        Connection conn=driver.connect(url,info);
  • 下面就是把Driver注册到了DriverManager进行管理,但是也不推荐这种方式
代码语言:javascript
复制
        String url="jdbc:mysql://localhost:3306/test1";
        String user="root";
        String password="xxx";
        Class clazz=Class.forName("com.mysql.jdbc.Driver");
        Driver driver=(Driver)clazz.newInstance();
        //注册驱动
        DriverManager.registerDriver(driver);
        //获取连接
        Connection conn=DriverManager.getConnection(url,user,password);
  • 下面这种方式相对来说就舒服很多,但是其实还可以更简单
代码语言:javascript
复制
        String url="jdbc:mysql://localhost:3306/test1";
        String user="root";
        String password="xxx";
        //加载Driver
       Class clazz=Class.forName("com.mysql.jdbc.Driver");
        //获取连接
        Connection conn=DriverManager.getConnection(url,user,password);
  • 下面一行代码就够了
代码语言:javascript
复制
        Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/test1",
                "root", "xxx");

相信大家看着我一步步简化到最后,已经蒙了,为什么可以这样写,别急,下面我们就来看看DriverManager到底是怎么实现的


ServiceLoader

因为DriverManager实现主要依靠了ServiceLoader来完成,因此这里先来看看ServiceLoader干了什么:

在Java应用中存在着很多服务提供者接口(Service Provider Interface,SPI),这些接口允许第三方为它们提供实现,如常见的 SPI 有 JDBC、JNDI等,这些 SPI 的接口属于 Java 核心库,一般存在rt.jar包中,由Bootstrap类加载器加载,而 SPI 的第三方实现代码则是作为Java应用所依赖的 jar 包被存放在classpath路径下,由于SPI接口中的代码经常需要加载具体的第三方实现类并调用其相关方法,但SPI的核心接口类是由引导类加载器来加载的,而Bootstrap类加载器无法直接加载SPI的实现类,同时由于双亲委派模式的存在,Bootstrap类加载器也无法反向委托AppClassLoader加载器SPI的实现类。在这种情况下,我们就需要一种特殊的类加载器来加载第三方的类库,而线程上下文类加载器就是很好的选择。

线程上下文类加载器(contextClassLoader)是从 JDK 1.2 开始引入的,我们可以通过java.lang.Thread类中的getContextClassLoader()和 setContextClassLoader(ClassLoader cl)方法来获取和设置线程的上下文类加载器。

如果没有手动设置上下文类加载器,线程将继承其父线程的上下文类加载器,初始线程的上下文类加载器是系统类加载器(AppClassLoader),在线程中运行的代码可以通过此类加载器来加载类和资源,如下图所示,以jdbc.jar加载为例

从图可知rt.jar核心包是有Bootstrap类加载器加载的,其内包含SPI核心接口类,由于SPI中的类经常需要调用外部实现类的方法,而jdbc.jar包含外部实现类(jdbc.jar存在于classpath路径)无法通过Bootstrap类加载器加载,因此只能委派线程上下文类加载器把jdbc.jar中的实现类加载到内存以便SPI相关类使用。显然这种线程上下文类加载器的加载方式破坏了“双亲委派模型”,它在执行过程中抛弃双亲委派加载链模式,使程序可以逆向使用类加载器,当然这也使得Java类加载器变得更加灵活。

例如我们这里要介绍的DriverManager,它是Java核心rt.jar包中的类,该类用来管理不同数据库的实现驱动即Driver,它们都实现了Java核心包中的java.sql.Driver接口,如mysql驱动包中的com.mysql.jdbc.Driver。

而因为DriverManager是由启动类加载器进行加载的,启动类加载器无法去加载类路径下的Driver接口实现类,因此需要将加载这些实现类的需求委托给线程上下文类加载器来完成,实际是通过ServiceLoader调用线程上下文类加载器去加载这些接口实现类的。

下面就来看看ServiceLoader是如何加载的吧。


源码分析

该类中有一个静态变量,指明了ServiceLoader会去哪里寻找需要被加载的类:

代码语言:javascript
复制
//会去每个类路径下的META-INF/services/包下,寻找需要被加载的类
   private static final String PREFIX = "META-INF/services/";

还有其他一些属性如下:

代码语言:javascript
复制
    //需要被加载的类,这里给出的是接口,然后在类路径下寻找其实现类
    private final Class<S> service;

    //用来寻找和加载实现类的类加载器,即为线程上下文类加载器
    private final ClassLoader loader;

    //存放所找到的所有可用的实现类
    private LinkedHashMap<String,S> providers = new LinkedHashMap<>();

    // 用来遍历上面这个集合的迭代器,当然没那么简单,它还会负责类的加载
    private LazyIterator lookupIterator;

下面来看看其中的一些方法,首先是关于如何获取一个ServiceLoader实例的方法:

代码语言:javascript
复制
//重新加载ServiceLoader
    public void reload() {
    //清空集合
        providers.clear();
        lookupIterator = new LazyIterator(service, loader);
    }
//构造器为私有,说明是单例模式
    private ServiceLoader(Class<S> svc, ClassLoader cl) {
        service = Objects.requireNonNull(svc, "Service interface cannot be null");
        //默认是线程上下文类加载器
        loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
        acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
        //重新加载一下
        reload();
    }

那么如何获取这个单例的ServiceLoader呢?

代码语言:javascript
复制
    //我们一般调用该方法来加载某个接口提供的所有实现类
    public static <S> ServiceLoader<S> load(Class<S> service) {
        //默认就是线程上下文类加载器
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        return ServiceLoader.load(service, cl);
    }
     
    public static <S> ServiceLoader<S> load(Class<S> service,
                                            ClassLoader loader)
    {
       //返回一个 ServiceLoader实例,当然这里不算是单例模式
        return new ServiceLoader<>(service, loader);
    }

ServiceLoader的创建过程分析完了,下面来分析一下它是如何定位到那些实现类并进行初始化的吧:

一般都是调用它的iterator()方法返回一个LazyIterator迭代器:

代码语言:javascript
复制
    public Iterator<S> iterator() {
        return new Iterator<S>() {

            Iterator<Map.Entry<String,S>> knownProviders
                = providers.entrySet().iterator();
           
           //是否还有下一个元素 
            public boolean hasNext() {
                if (knownProviders.hasNext())
                    return true;
                return lookupIterator.hasNext();
            }
           
           //拿到下一个元素
            public S next() {
                if (knownProviders.hasNext())
                    return knownProviders.next().getValue();
                //真正的核心在这里
                return lookupIterator.next();
            }

            public void remove() {
                throw new UnsupportedOperationException();
            }

        };
    }

下面看看LazyIterator是怎么实现的吧:

代码语言:javascript
复制
        public boolean hasNext() {
          ...
          //核心方法
                return hasNextService();
         ....
        }
        
        public S next() {
         ...
        //核心方法
                return nextService();
        ...
        }

核心方法

代码语言:javascript
复制
private boolean hasNextService() {
            if (nextName != null) {
                return true;
            }
            if (configs == null) {
                try {
                //PREFIX就是"META-INF/services/"
                //service.getName()就是接口全类名,如果是Drive接口
                //那么就是去类路径下寻找所有的
                //META-INF/services/java.sql.Driver
                    String fullName = PREFIX + service.getName();
               //去类路径下加载,这里类加载器就是线程上下文类加载器     
                    if (loader == null)
                        configs = ClassLoader.getSystemResources(fullName);
                    else
                        configs = loader.getResources(fullName);
                } catch (IOException x) {
                    fail(service, "Error locating configuration files", x);
                }
            }
            
            while ((pending == null) || !pending.hasNext()) {
                if (!configs.hasMoreElements()) {
                    return false;
                }
                pending = parse(service, configs.nextElement());
            }
            nextName = pending.next();
            return true;
        }
代码语言:javascript
复制
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
        }

小结

ServiceLoader核心思路去是去类路径寻找META-INF/services/接口全类名这样一个文件,该文件里面记录了实现类的全类名,拿到实现类的全类名后,再去实例化实现类,然后返回所有实现类集合。

代码语言:javascript
复制
            //只有上面实例化没出错,这里才会创建实例,然后返回
            //在这里类会被初始化---注意这里---对应的实现类被初始化
            //那么对应的实现类内部的静态代码块会被执行--一行有很大作用
                S p = service.cast(c.newInstance());

实例化过程中,对应的实现类的静态代码块会被调用,因此我们可以在实现类的静态代码块中做些手脚,而DriverManager实际上就是靠这个手脚,完成实现类到DriverManager的注册的,下面我们会看到。


DriverManager

DriverManager中用一个List集合记录了所有注册到自己身上的驱动类:

代码语言:javascript
复制
    // List of registered JDBC drivers
    private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();

DriverManager类中有一个静态代码块:

代码语言:javascript
复制
    static {
       //加载并初始化所有驱动类,就是靠上面的ServiceLoader完成的
        loadInitialDrivers();
        println("JDBC DriverManager initialized");
    }

loadInitialDrivers–加载并初始化所有驱动类

代码语言:javascript
复制
private static void loadInitialDrivers() {
 //尝试去环境变量中寻找对应的驱动类全类名,如果有多个,按照:分割
        String drivers;
        try {
            drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
                public String run() {
                    return System.getProperty("jdbc.drivers");
                }
            });
        } catch (Exception ex) {
            drivers = null;
        }
      
        
        AccessController.doPrivileged(new PrivilegedAction<Void>() {
            public Void run() {
               //通过ServiceLoader按照SPI规范去寻找所有可能存在的驱动实现类
                ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
                Iterator<Driver> driversIterator = loadedDrivers.iterator();
                try{
                //hasNext实际调用hasNextService-去类路径下
                //寻找所有的META-INF/services/java.sql.Driver文件
                    while(driversIterator.hasNext()) {
                    //next实际调用nextService
                    //该方法会实例化实现类
                        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);
                //初始化这些实现类,第二个参数为true,表示会执行初始化过程
                Class.forName(aDriver, true,
                        ClassLoader.getSystemClassLoader());
            } catch (Exception ex) {
                println("DriverManager.Initialize: load failed: " + ex);
            }
        }
    }

loadInitialDrivers方法执行过后,registeredDrivers 集合中就已经有了所有能找到的驱动实现类集合, 但是上面都没看到有往集合中添加驱动的代码,怎么集合就有元素了呢?

那是因为驱动实现类的静态代码块会在初始化的时候被调用,然后往registeredDrivers注册自己。


registerDriver—注册驱动

registerDriver就是用来将驱动实现类放入DriverManager的registeredDrivers集合中的

代码语言:javascript
复制
    public static synchronized void registerDriver(java.sql.Driver driver,
            DriverAction da)
        throws SQLException {

        /* Register the driver if it has not already been added to our list */
        if(driver != null) {
            registeredDrivers.addIfAbsent(new DriverInfo(driver, da));
        } else {
            // This is for compatibility with the original DriverManager
            throw new NullPointerException();
        }

        println("registerDriver: " + driver);

    }

该方法会在实现类的静态代码块中被调用,例如:

代码语言:javascript
复制
package com.mysql.jdbc;

import java.sql.DriverManager;
import java.sql.SQLException;

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    public Driver() throws SQLException {
    }

    static {
        try {
        //当Mysql的驱动被初始化时,静态代码块会执行,然后就会向DriverManager注册自己
            DriverManager.registerDriver(new Driver());
        } catch (SQLException var1) {
            throw new RuntimeException("Can't register driver!");
        }
    }
}

getConnection—获取数据库连接

代码语言:javascript
复制
    private static Connection getConnection(
        String url, java.util.Properties info, Class<?> caller) throws SQLException {
        ...
        //遍历驱动实现类集合,挨个尝试连接,第一个成功连接后,直接返回
        for(DriverInfo aDriver : registeredDrivers) {
            if(isDriverAllowed(aDriver.driver, callerCL)) {
                try {
                    println("    trying " + aDriver.driver.getClass().getName());
                    //连接,返回connection
                    Connection con = aDriver.driver.connect(url, info);
                    //如果返回的不为null,说明连接成功,直接返回
                    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 we got here nobody could connect.
        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");
    }
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-06-05,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • ServiceLoader和DriverManager的前世今身
  • JDBC获取数据库连接的方式
  • ServiceLoader
    • 源码分析
      • 核心方法
      • 小结
  • DriverManager
    • loadInitialDrivers–加载并初始化所有驱动类
      • registerDriver—注册驱动
        • getConnection—获取数据库连接
        相关产品与服务
        数据库
        云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档