JDBC驱动加载机制详解以及spi机制

首先有两个问题:

1、java连接数据库时是否真的需要加载驱动?

2、JDBC如何区分多个驱动?

 

以下摘自:https://blog.csdn.net/buqutianya/article/details/78936947

Class.forName作用

我们都知道,也听了无数遍,驱动的加载是由Class.forName 方法完成的。

但是,让我们深究一下,Class.forName是JSE里面加载一个类到JVM内存的方法,为什么又会关联了JDBC的驱动加载逻辑呢?

如果是的话,那么其他和JDBC无关的类加载也会调用JDBC驱动加载的相关逻辑吗?

是不是觉得问题很大,是不是觉得如果是我们来说设计JSE也不会做这样的事情?

给Class.forName正名

确实JDBC驱动的加载是在Class.forName这一步完成的,但是完成这个工作的是加载的具体的数据库驱动类的静态初始化块完成的。

这里看一下mysql的驱动类的代码:

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    //
    // Register ourselves with the DriverManager
    //
    static {
        try {
            java.sql.DriverManager.registerDriver(new Driver());
        } catch (SQLException E) {
            throw new RuntimeException("Can't register driver!");
        }
    }
}

由于JVM对类的加载有一个逻辑是:在类被需要的时候,或者首次调用的时候就会把类加载到JVM。反过来也就是:如果类没有被需要的时候,一般是不会被加载到JVM的。

当连接数据库的时候我们调用了Class.forName语句之后,数据库驱动类被加载到JVM,那么静态初始化块就会被执行,从而完成驱动的注册工作,也就是注册到了JDBC的DriverManager类中。

由于是静态初始化块中完成的加载,所以也就不必担心驱动被加载多次,原因可以参考单例模式相关的知识。

抛弃Class.forName

在JDBC 4.0之后实际上我们不需要再调用Class.forName来加载驱动程序了,我们只需要把驱动的jar包放到工程的类加载路径里,那么驱动就会被自动加载。

这个自动加载采用的技术叫做SPI,数据库驱动厂商也都做了更新。可以看一下jar包里面的META-INF/services目录,里面有一个java.sql.Driver的文件,文件里面包含了驱动的全路径名。

比如mysql-connector里面的内容:

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

那么SPI技术又是在什么阶段加载的数据库驱动呢?看一下JDBC的DriverManager类就知道了。

public class DriverManager {
    static {
        loadInitialDrivers();//......1
        println("JDBC DriverManager initialized");
    }

    private static void loadInitialDrivers() {
        String drivers;
        try {
            drivers = AccessController.doPrivileged(new PrivilegedAction() {
                public String run() {
                 return System.getProperty("jdbc.drivers");
                }
                });
           } catch (Exception ex) {
                drivers = null;
           }

           AccessController.doPrivileged(new PrivilegedAction() {
                public Void run() {
                    ServiceLoader loadedDrivers = ServiceLoader.load(Driver.class);//.....2
                   Iterator driversIterator = loadedDrivers.iterator();

                //.....
}

上述代码片段标记…1的位置是在DriverManager类加载是执行的静态初始化块,这里会调用loadInitialDrivers方法。

再看loadInitialDrivers方法里面标记…2的位置,这里调用的 ServiceLoader.load(Driver.class); 就会加载所有在META-INF/services/java.sql.Driver文件里边的类到JVM内存,完成驱动的自动加载。

这就是SPI的优势所在,能够自动的加载类到JVM内存。这个技术在阿里的dubbo框架里面也占到了很大的分量,有兴趣的朋友可以看一下dubbo的代码,或者百度一下dubbo的扩展机制。

JDBC如何区分多个驱动?

一个项目里边很可能会即连接MySQL,又连接Oracle,这样在一个工程里边就存在了多个驱动类,那么这些驱动类又是怎么区分的呢?

关键点就在于getConnection的步骤,DriverManager.getConnection中会遍历所有已经加载的驱动实例去创建连接,当一个驱动创建连接成功时就会返回这个连接,同时不再调用其他的驱动实例。DriverManager关键代码如下:

private static Connection getConnection(
    //.....

    for(DriverInfo aDriver : registeredDrivers) {
        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());
        }
     }

    //......

是不是每个驱动实例都真真实实的要尝试建立连接呢?不是的!

每个驱动实例在getConnetion的第一步就是按照url判断是不是符合自己的处理规则,是的话才会和db建立连接。比如,MySQL驱动类中的关键代码:

    public boolean acceptsURL(String url) throws SQLException {
        return (parseURL(url, null) != null);
    }

    public Properties parseURL(String url, Properties defaults)
            throws java.sql.SQLException {
        Properties urlProps = (defaults != null) ? new Properties(defaults)
                : new Properties();

        if (url == null) {
            return null;
        }

        if (!StringUtils.startsWithIgnoreCase(url, URL_PREFIX)
                && !StringUtils.startsWithIgnoreCase(url, MXJ_URL_PREFIX)
                && !StringUtils.startsWithIgnoreCase(url,
                        LOADBALANCE_URL_PREFIX)
                && !StringUtils.startsWithIgnoreCase(url,
                        REPLICATION_URL_PREFIX)) { //$NON-NLS-1$

            return null;
        }
        //......

 

以下摘自:https://blog.csdn.net/sigangjun/article/details/79071850

1 SPI机制简介

    SPI的全名为Service Provider Interface.大多数开发人员可能不熟悉,因为这个是针对厂商或者插件的。在java.util.ServiceLoader的文档里有比较详细的介绍。简单的总结下java spi机制的思想。我们系统里抽象的各个模块,往往有很多不同的实现方案,比如日志模块的方案,xml解析模块、jdbc模块的方案等。面向的对象的设计里,我们一般推荐模块之间基于接口编程,模块之间不对实现类进行硬编码。一旦代码里涉及具体的实现类,就违反了可拔插的原则,如果需要替换一种实现,就需要修改代码。为了实现在模块装配的时候能不在程序里动态指明,这就需要一种服务发现机制。 java spi就是提供这样的一个机制:为某个接口寻找服务实现的机制。有点类似IOC的思想,就是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要。

 

2 SPI具体约定

    java spi的具体约定为:当服务的提供者,提供了服务接口的一种实现之后,在jar包的META-INF/services/目录里同时创建一个以服务接口命名的文件。该文件里就是实现该服务接口的具体实现类。而当外部程序装配这个模块的时候,就能通过该jar包META-INF/services/里的配置文件找到具体的实现类名,并装载实例化,完成模块的注入。 基于这样一个约定就能很好的找到服务接口的实现类,而不需要再代码里制定。jdk提供服务实现查找的一个工具类:java.util.ServiceLoader

 

3 应用场景

    1.common-logging    apache最早提供的日志的门面接口。只有接口,没有实现。具体方案由各提供商实现, 发现日志提供商是通过扫描 META-INF/services/org.apache.commons.logging.LogFactory配置文件,通过读取该文件的内容找到日志提工商实现类。只要我们的日志实现里包含了这个文件,并在文件里制定 LogFactory工厂接口的实现类即可。    2.jdbc    jdbc4.0以前, 开发人员还需要基于Class.forName("xxx")的方式来装载驱动,jdbc4也基于spi的机制来发现驱动提供商了,可以通过META-INF/services/java.sql.Driver文件里指定实现类的方式来暴露驱动提供者.

 

 

你可能感兴趣的:(Java语法,Java语法)