首页 > 数据库 >探究MySQL8.0驱动的加载

探究MySQL8.0驱动的加载

时间:2024-04-08 14:47:32浏览次数:17  
标签:return service 探究 MySQL8.0 new null class 加载

126094-zui-yi_shu-shou_shi-ji_qie-chuang_zao_xing_de_yi_shu-1920x1080

探究MySQL8.0驱动的加载

大家在连接mysql的时候,启动项目,会警告你推荐使用com.mysql.cj.jdbc.Driver 而不是com.mysql.jdbc.Driver

那么这两者到底有什么区别呢

本质区别:

com.mysql.jdbc.Driver 是 mysql-connector-java 5中的,需要手动加载驱动
com.mysql.cj.jdbc.Driver 是 mysql-connector-java 6以及以上中的,不需要手动加载驱动

驱动的加载

在低版本驱动中,我们通常使用class.forName("com.mysql.jdbc.Driver")来手动加载驱动

但在高版本中这段代码却被省略了,例如下面这段代码,并没有手动加载驱动,却依旧可以正常执行,那么MySQL驱动是如何被加载的呢?

public class Test {
    public static void main(String[] args) throws Exception{
        // 数据库地址 + 要使用的数据库名
        String url = "jdbc:mysql://localhost:3306/" + "test";
        // 你要登陆的数据库用户,一般为 root
        String user = "root";
        // 你要登录用户的密码
        String password = "12345678";
        DriverManager.setLogWriter(new PrintWriter(System.out));
        Connection con = DriverManager.getConnection(url, user, password);
        // 输出连接
        System.out.println(con);
        // 关闭连接
        con.close();
    }
}

点进去DriverManager.getConnection,并没有发现有用的信息

    @CallerSensitive
    public static Connection getConnection(String url,
        String user, String password) throws SQLException {
        java.util.Properties info = new java.util.Properties();

        if (user != null) {
            info.put("user", user);
        }
        if (password != null) {
            info.put("password", password);
        }

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

我们顺藤摸瓜继续点(getConnection(url, info, Reflection.getCallerClass()));,发现如下代码

private static Connection getConnection(
        String url, java.util.Properties info, Class<?> caller) throws SQLException {
        /*
         * When callerCl is null, we should check the application's
         * (which is invoking this class indirectly)
         * classloader, so that the JDBC driver class outside rt.jar
         * can be loaded from here.
         */
        ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
        if (callerCL == null || callerCL == ClassLoader.getPlatformClassLoader()) {
            callerCL = Thread.currentThread().getContextClassLoader();
        }

        if (url == null) {
            throw new SQLException("The url cannot be null", "08001");
        }

        println("DriverManager.getConnection(\"" + url + "\")");
    	//--------------------------------------------------------------------
		//其他的都和驱动的自动加载没什么关系,只有这个才是重点
        ensureDriversInitialized();
		//--------------------------------------------------------------------
    
        // Walk through the loaded registeredDrivers attempting to make a connection.
        // Remember the first exception that gets raised so we can reraise it.
        SQLException reason = null;

    	//
        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.driver.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");
    }

ensureDriversInitialized();翻译过来就是 确保驱动程序已初始化 我们点进去看看

private static void ensureDriversInitialized() {
        if (driversInitialized) {
            return;
        }

        synchronized (lockForInitDrivers) {
            if (driversInitialized) {
                return;
            }
            String drivers;
            try {
                drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
                    public String run() {
                        return System.getProperty(JDBC_DRIVERS_PROPERTY);
                    }
                });
            } 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.isEmpty()) {
                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);
                    }
                }
            }

            //注意这里(driversInitialized=驱动程序已初始化),这是DriverManager的一个私有字段
            driversInitialized = true;
            println("JDBC DriverManager initialized");
        }
    }

image-20240406204907448

这里明确把driversInitialized标为true说明驱动在它之前就加载好了,那么我们往上看,这里有个同步代码块

synchronized (lockForInitDrivers) {
            if (driversInitialized) {
                return;
            }
            String drivers;
            try {
                drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
                    public String run() {
                        return System.getProperty(JDBC_DRIVERS_PROPERTY);
                    }
                });
            } 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.
    		//公开为 java.sql.Driver.class 服务。
            // ServiceLoader.load() replaces the sun.misc.Providers()
    		//ServiceLoader.load() 替换了 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.
                     *加载这些驱动程序,以便可以实例化它们。可能不存在驱动程序类的情况,即可能有一个打包的驱动程序,其中服务类作为 java.sql.Driver 的实现,但实际类可能丢失。在这种情况下,尝试查找和加载服务的 VM 将在运行时抛出 java.util.ServiceConfigurationError。
                     * 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 catch 块以捕获这些运行时错误。
                     */
                    try {
                        while (driversIterator.hasNext()) {
                            driversIterator.next();
                        }
                    } catch (Throwable t) {
                        // Do nothing
                    }
                    return null;
                }
            });

通过断点调试我们发现,在这里 while (driversIterator.hasNext())经历过一次循环后registeredDrivers(DriverManager的私有变量,用于存储所有注册的驱动程序)的size变为 1 ,这说明driversIterator在进行便利的时候加载Driver这个类

image-20240406210148049

image-20240406210226968

往上找,我们在注释上面有这两行代码,注释也表明就是在这里加载驱动的,我们点进去了解一下

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

在这里同个静态方法创建ServiceLoader了这个类,继续往下追踪

    @CallerSensitive
    public static <S> ServiceLoader<S> load(Class<S> service) {
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        return new ServiceLoader<>(Reflection.getCallerClass(), service, cl);
    }

ServiceLoader的一些初始化,没有发现什么有用的信息

private ServiceLoader(Class<?> caller, Class<S> svc, ClassLoader cl) {
        Objects.requireNonNull(svc);

        if (VM.isBooted()) {
            checkCaller(caller, svc);
            if (cl == null) {
                cl = ClassLoader.getSystemClassLoader();
            }
        } else {

            // if we get here then it means that ServiceLoader is being used
            //如果我们到达这里,则意味着正在使用 ServiceLoader
            // before the VM initialization has completed. At this point then
            //在 VM 初始化完成之前。在这一点上
            // only code in the java.base should be executing.
            //只有 java.base 中的代码应该执行。
            Module callerModule = caller.getModule();
            Module base = Object.class.getModule();
            Module svcModule = svc.getModule();
            if (callerModule != base || svcModule != base) {
                fail(svc, "not accessible to " + callerModule + " during VM init");
            }

            // restricted to boot loader during startup
            cl = null;
        }

        this.service = svc;
        this.serviceName = svc.getName();
        this.layer = null;
        this.loader = cl;
        this.acc = (System.getSecurityManager() != null)
                ? AccessController.getContext()
                : null;
    }

我们再看这一行代码,点进去loadedDrivers.iterator()了解一下

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

这里有一个迭代器内部类,返回了一个匿名类

public Iterator<S> iterator() {

        // create lookup iterator if needed  如果需要,创建查找迭代器
        if (lookupIterator1 == null) {
            lookupIterator1 = newLookupIterator();
        }

        return new Iterator<S>() {

            // record reload count  记录重新加载计数
            final int expectedReloadCount = ServiceLoader.this.reloadCount;

            // index into the cached providers list  索引到缓存的提供程序列表中
            int index;

            /**
             * Throws ConcurrentModificationException if the list of cached
             * providers has been cleared by reload.
             *如果缓存的提供程序列表已被重新加载清除,则引发 ConcurrentModificationException。
             */
            private void checkReloadCount() {
                if (ServiceLoader.this.reloadCount != expectedReloadCount)
                    throw new ConcurrentModificationException();
            }

            @Override
            public boolean hasNext() {
                checkReloadCount();
                if (index < instantiatedProviders.size())
                    return true;
                return lookupIterator1.hasNext();
            }

            @Override
            public S next() {
                checkReloadCount();
                S next;
                if (index < instantiatedProviders.size()) {
                    next = instantiatedProviders.get(index);
                } else {
                    next = lookupIterator1.next().get();
                    instantiatedProviders.add(next);
                }
                index++;
                return next;
            }

        };
    }

注意这里创建了一个查找迭代器,会被下面返回的匿名迭代器使用,点进去newLookupIterator看看

 // create lookup iterator if needed  如果需要,创建查找迭代器
        if (lookupIterator1 == null) {
            lookupIterator1 = newLookupIterator();
        }

断点调试走了else这条分支,发现这里创建了两个查找迭代器,并返回一个迭代器匿名类,而且这两个查找迭代器由于作用域的关系还成了这个匿名类的私有属性(类似闭包),我们分别查看这两个查找的迭代器

private Iterator<Provider<S>> newLookupIterator() {
        assert layer == null || loader == null;
        if (layer != null) {
            return new LayerLookupIterator<>();
        } else {
            								//模块服务查找迭代器
            Iterator<Provider<S>> first = new ModuleServicesLookupIterator<>();
            								//惰性类路径查找迭代器
            Iterator<Provider<S>> second = new LazyClassPathLookupIterator<>();
            return new Iterator<Provider<S>>() {
                @Override
                public boolean hasNext() {
                    return (first.hasNext() || second.hasNext());
                }
                @Override
                public Provider<S> next() {
                    if (first.hasNext()) {
                        return first.next();
                    } else if (second.hasNext()) {
                        return second.next();
                    } else {
                        throw new NoSuchElementException();
                    }
                }
            };
        }
    }

LazyClassPathLookupIterator中发现了端倪static final String PREFIX = "META-INF/services/",查看 mysql jar包路径下的META-INF/services/文件

private final class LazyClassPathLookupIterator<T>
        implements Iterator<Provider<T>>
    {
        static final String PREFIX = "META-INF/services/";

        Set<String> providerNames = new HashSet<>();  // to avoid duplicates  避免重复
        Enumeration<URL> configs;
        Iterator<String> pending;

        Provider<T> nextProvider;
        ServiceConfigurationError nextError;

        LazyClassPathLookupIterator() { }

        /**
         * Parse a single line from the given configuration file, adding the
         * name on the line to set of names if not already seen.
         从给定的配置文件中解析一行,如果尚未看到,则将该行的名称添加到一组名称中。
         */
        private int parseLine(URL u, BufferedReader r, int lc, Set<String> names)
            throws IOException
        {
            String ln = r.readLine();
            if (ln == null) {
                return -1;
            }
            int ci = ln.indexOf('#');
            if (ci >= 0) ln = ln.substring(0, ci);
            ln = ln.trim();
            int n = ln.length();
            if (n != 0) {
                if ((ln.indexOf(' ') >= 0) || (ln.indexOf('\t') >= 0))
                    fail(service, u, lc, "Illegal configuration-file syntax");
                int cp = ln.codePointAt(0);
                if (!Character.isJavaIdentifierStart(cp))
                    fail(service, u, lc, "Illegal provider-class name: " + ln);
                int start = Character.charCount(cp);
                for (int i = start; i < n; i += Character.charCount(cp)) {
                    cp = ln.codePointAt(i);
                    if (!Character.isJavaIdentifierPart(cp) && (cp != '.'))
                        fail(service, u, lc, "Illegal provider-class name: " + ln);
                }
                if (providerNames.add(ln)) {
                    names.add(ln);
                }
            }
            return lc + 1;
        }

        /**
         * Parse the content of the given URL as a provider-configuration file.
         将给定 URL 的内容解析为提供程序配置文件
         */
        private Iterator<String> parse(URL u) {
            Set<String> names = new LinkedHashSet<>(); // preserve insertion order 保留插入顺序
            try {
                URLConnection uc = u.openConnection();
                uc.setUseCaches(false);
                try (InputStream in = uc.getInputStream();
                     BufferedReader r
                         = new BufferedReader(new InputStreamReader(in, UTF_8.INSTANCE)))
                {
                    int lc = 1;
                    while ((lc = parseLine(u, r, lc, names)) >= 0);
                }
            } catch (IOException x) {
                fail(service, "Error accessing configuration file", x);
            }
            return names.iterator();
        }

        /**
         * Loads and returns the next provider class.  加载并返回下一个提供程序类
         */
        private Class<?> nextProviderClass() {
            if (configs == null) {
                try {
                    String fullName = PREFIX + service.getName();
                    if (loader == null) {
                        configs = ClassLoader.getSystemResources(fullName);
                    } else if (loader == ClassLoaders.platformClassLoader()) {
                        // The platform classloader doesn't have a class path,
                        // but the boot loader might.
                        //平台类加载器没有类路径,但引导加载器可能
                        if (BootLoader.hasClassPath()) {
                            configs = BootLoader.findResources(fullName);
                        } else {
                            configs = Collections.emptyEnumeration();
                        }
                    } else {
                        configs = loader.getResources(fullName);
                    }
                } catch (IOException x) {
                    fail(service, "Error locating configuration files", x);
                }
            }
            while ((pending == null) || !pending.hasNext()) {
                if (!configs.hasMoreElements()) {
                    return null;
                }
                pending = parse(configs.nextElement());
            }
            String cn = pending.next();
            try {
                return Class.forName(cn, false, loader);
            } catch (ClassNotFoundException x) {
                fail(service, "Provider " + cn + " not found");
                return null;
            }
        }

        @SuppressWarnings("unchecked")
        private boolean hasNextService() {
            while (nextProvider == null && nextError == null) {
                try {
                    Class<?> clazz = nextProviderClass();
                    if (clazz == null)
                        return false;

                    if (clazz.getModule().isNamed()) {
                        // ignore class if in named module  如果在命名模块中,则忽略类
                        continue;
                    }

                    if (service.isAssignableFrom(clazz)) {
                        Class<? extends S> type = (Class<? extends S>) clazz;
                        Constructor<? extends S> ctor
                            = (Constructor<? extends S>)getConstructor(clazz);
                        ProviderImpl<S> p = new ProviderImpl<S>(service, type, ctor, acc);
                        nextProvider = (ProviderImpl<T>) p;
                    } else {
                        fail(service, clazz.getName() + " not a subtype");
                    }
                } catch (ServiceConfigurationError e) {
                    nextError = e;
                }
            }
            return true;
        }

        private Provider<T> nextService() {
            if (!hasNextService())
                throw new NoSuchElementException();

            Provider<T> provider = nextProvider;
            if (provider != null) {
                nextProvider = null;
                return provider;
            } else {
                ServiceConfigurationError e = nextError;
                assert e != null;
                nextError = null;
                throw e;
            }
        }

        @SuppressWarnings("removal")
        @Override
        public boolean hasNext() {
            if (acc == null) {
                return hasNextService();
            } else {
                PrivilegedAction<Boolean> action = new PrivilegedAction<>() {
                    public Boolean run() { return hasNextService(); }
                };
                return AccessController.doPrivileged(action, acc);
            }
        }

        @SuppressWarnings("removal")
        @Override
        public Provider<T> next() {
            if (acc == null) {
                return nextService();
            } else {
                PrivilegedAction<Provider<T>> action = new PrivilegedAction<>() {
                    public Provider<T> run() { return nextService(); }
                };
                return AccessController.doPrivileged(action, acc);
            }
        }
    }

在此路径下确实找到了com.mysql.cj.jdbc.Driver的全限定名

image-20240406223438558

断点调试,也确实发现它拿到了驱动类的全限定名,剩下的也就是加载驱动了,不在探究

image-20240406224605817

总结

DriverManager通过扫描每个包下的META-INF/services/的对应文件获取驱动类的去限定名,然后再去加载它

有兴趣可以去了解一下 SPI(Service Provider Interface,服务提供接口) (对你可能有帮助)

推荐文章:【Java】SPI介绍及实例分析_spi 实例讲解-CSDN博客

标签:return,service,探究,MySQL8.0,new,null,class,加载
From: https://www.cnblogs.com/starychen/p/18121097

相关文章

  • VSCode 终端显示“pnpm : 无法加载文件 C:\Program Files\nodejs\npm.ps1,因为在此
    解决方案:1.用get-ExecutionPolicy命令在vscode终端查询状态get-ExecutionPolicy返回Restricted说明状态是禁止的2.用set-ExecutionPolicyRemoteSigned命令更改状态即可set-ExecutionPolicyRemoteSigned此时再输入get-ExecutionPolicy,显示RemoteSigned即可正常执......
  • Mysql8.0高可用部署
    Mysql8.0高可用部署前言目前是三台服务器,每台服务器部署mysql、mgr、proxysql、keepalived,mysql8.0是通过mgr的方式来实现mysql服务的高可用,实现故障自动检测及自动切换,发生故障时能自动切换到新的主节点,但是当程序调用的mysql对应的服务宕机后,无法自动切换到正常服务上面......
  • 探究MySQL8.0驱动的加载
    探究MySQL8.0驱动的加载大家在连接mysql的时候,启动项目,会警告你推荐使用com.mysql.cj.jdbc.Driver而不是com.mysql.jdbc.Driver那么这两者到底有什么区别呢本质区别:com.mysql.jdbc.Driver是mysql-connector-java5中的,需要手动加载驱动com.mysql.cj.jdbc.Driver是mysql......
  • 探究MySQL8.0驱动的加载
    探究MySQL8.0驱动的加载大家在连接mysql的时候,启动项目,会警告你推荐使用com.mysql.cj.jdbc.Driver而不是com.mysql.jdbc.Driver那么这两者到底有什么区别呢本质区别:com.mysql.jdbc.Driver是mysql-connector-java5中的,需要手动加载驱动com.mysql.cj.jdbc.Driver是mysql-......
  • 2-40. 实现人物跨场景移动以及场景加载前后事件
    解决上节课加载场景出现的报错问题下面两个地方需要在场景加载之后进行处理修改EventHandler修改TransitionManager修改SwitchBounds修改TransitionManager让人物在场景加载后才能移动修改EventHandler加载完场景移动人物坐标增加变量控制人物移动注册事......
  • webpack前端模块加载工具
    webpack前端模块加载工具 最近在看许多React的资料,发现了大部分的项目都是用webpack行模块化管理的工具。这次也是借着写了一个React-Todos的小应用,对webPack最基本实用的功能体验了一番,顺带做个小记录。为什么用webpackCommonJs与AMD在一开始,我们先讲一下它和以往我们所......
  • SAP HCM 逻辑数据 动态加载INFOTYPES
    NFOTYPES如何与$rinfo关联关系$!!! 前几天在群里面看到讨论"请教大家,一般使用逻辑数据库pnp的程序,需要读取的infotype在程序中用关键字infotypes声明,但是我发现有些程序读取infotype居然可以是动态的,大家知道这个额外控制的机理是什么吗?"周末有找到一个查找CODE_SCANN......
  • 在Linux中,内核模块是什么以及如何加载和卸载它们?
    在Linux中,内核模块是内核的一部分,但它们不是编译进内核的代码。这些模块可以在运行时动态地加载到内核中,或者从内核中卸载,从而扩展或修改内核的功能而无需重启系统。内核模块的使用提供了灵活性和易于维护的特点。1.内核模块的作用扩展内核功能:内核模块允许在不修改内核源代码......
  • go~istio加载wasm的步骤
    参考https://github.com/higress-group/proxy-wasm-go-sdk/tree/main/proxywasmhttps://github.com/tetratelabs/proxy-wasm-go-sdkhttps://github.com/alibaba/higress/blob/main/plugins/wasm-go/pkg/wrapperhttps://tinygo.org/docs/reference/https://tinygo.org/docs......
  • CentOS7安装MySQL8.0教程
    环境介绍操作系统:Centos7.6MySQL版本:8.0.27只要是8.0.*版本,那就可以按照本文说明安装一、安装前准备1、卸载MariaDB安装MySQL的话会和MariaDB的文件冲突,所以需要先卸载掉MariaDB。1.1、查看是否安装mariadbrpm-qa|grepmariadb1.2、卸载rpm-e--nodeps文件......