【JDBC】JDBC 中的 SPI 机制

那么,在 JDBC 中,SPI 机制是如何使用的呢?

如果还不了解 SPI 机制,请移步 【SPI】详解 Java 中的 SPI 机制

我们知道,在 JDBC 使用过程中,是有“注册驱动”这一步骤的,代码如下:

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

现在,我们将那个案例中的这行代码给注释/删除掉。即 getConnection() 方法代码如下:

public static Connection getConnection() {
    Connection conn = null;
    //try {
        //Class.forName("com.mysql.jdbc.Driver");
        try {
            conn = DriverManager.getConnection(JDBC_URL, USERNAME, PASSWORD);
            System.out.println(conn);
        } catch (SQLException e) {
            System.out.println("数据库连接失败");
        }
    //} catch (ClassNotFoundException e) {
        //System.out.println("驱动包不存在");
    //}
    return conn;
}

发现,也能查询数据库成功。

问题:为什么 JDBC 中不注册驱动还能连接数据库成功?

答案就在 SPI 机制中。


1. DriverManager 类

查看 DriverManager 类源码中的静态代码块:

public class DriverManager {
    /* Prevent the DriverManager class from being instantiated. */
    private DriverManager(){}

    /**
     * Load the initial JDBC drivers by checking the System property
     * jdbc.properties and then use the {@code ServiceLoader} mechanism
     */
    static {
        loadInitialDrivers();
        println("JDBC DriverManager initialized");
    }

查看 loadInitialDrivers() 方法:它在里面查找的是 Driver 接口的服务类,所以它的文件路径就是:META-INF/services/java.sql.Driver。并在调用 next() 方法时,会创建文件里面的实现类

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() {
			// 很明显,它要加载 Driver 接口的服务类,Driver接口的包为:java.sql.Driver
            // 所以它要找的就是 META-INF/services/java.sql.Driver 文件
            ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
            Iterator<Driver> driversIterator = loadedDrivers.iterator();
          
            try{
                // 查到之后创建对象
                while(driversIterator.hasNext()) {
                    driversIterator.next();
                }
            } catch(Throwable t) {
                // Do nothing
            }
            return null;
        }
    });
    ...
}

文件 META-INF/services/java.sql.Driver 位置:
【JDBC】JDBC 中的 SPI 机制_第1张图片
其内容为:

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

所以,会创建 com.mysql.jdbc.Driver 类、com.mysql.fabric.jdbc.FabricMySQLDriver 类


## 2. Driver 类 查看 com.mysql.jdbc.Driver 类源码:
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    public Driver() throws SQLException {
    }

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

在实例化 com.mysql.jdbc.Driver 对象时,会调用 Driver 类中的 static 代码块----向 DriverManager 注册自身的实例(Driver)。

好了,再回过头来看看 Java API 中对 DriverManager 类的说明:

The DriverManager methods getConnection and getDrivers have been enhanced to support the Java Standard Edition

Service Provider mechanism. JDBC 4.0 Drivers must include the file META-INF/services/java.sql.Driver. This file contains the name of the JDBC drivers implementation of java.sql.Driver. For example, to load the my.sql.Driver class,
the META-INF/services/java.sql.Driver file would contain the entry: my.sql.Driver.

Applications no longer need to explicitly load JDBC drivers using Class.forName(). Existing programs
which currently load JDBC drivers using Class.forName() will continue to work without
modification.

大致意思:JDBC 4.0驱动程序必须包含文件META-INF/services/java.sql.Driver 。 该文件包含java.sql.Driver的JDBC驱动程序实现的名称。应用程序不再需要使用Class.forName() 显式加载 JDBC 驱动程序。 当前使用 Class.forName() 加载 JDBC 驱动程序的现有程序将继续运行,而无需进行修改。

所以,现在明白了使用 JDBC 为什么可以不需要注册驱动了吧。


3. 创建 Connection 类

在DriverManager.getConnection() 方法就是创建连接的地方:它通过循环已注册的数据库驱动程序,调用其 connect() 方法,获取连接并返回。

private static Connection getConnection(String url, java.util.Properties info, Class<?> caller) throws SQLException {
   	...
    SQLException reason = null;
	// 遍历已注册的数据库驱动 Driver
    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());
        }

    }
    ...
}

4. 自定义驱动类 MyDriver

既然我们已经明白了数据库创建的过程,那么,我们可以尝试一下,自己创建一个驱动类 MyDriver。


按照之前的概念,总结出服务接口、服务实现:

服务接口(JDK):java.sql.Driver

服务实现:com.tinady.jdbc.MyDriver

按照 Java 中的 SPI 机制规范,需要做出以下准备工作:

  1. 需要在类路径下添加一个文件,文件名为:META-INF/services/java.sql.Driver
  2. 内容为:com.tinady.jdbc.MyDriver(包名 + 类名)

MyDriver 继承自 MySQL 中 的NonRegisteringDriver,还要实现 java.sql.Driver 接口。这样,在调用 connect() 方法的时候,就会调用到此类,但实际创建的过程还靠 MySQL 中的 NonRegisteringDriver 类完成。

public class MyDriver extends NonRegisteringDriver implements Driver {

    public MyDriver() throws SQLException {
    }

    static {
        try {
            DriverManager.registerDriver(new MyDriver());
        } catch (SQLException var1) {
            throw new RuntimeException("Can't register driver!");
        }
    }

    @Override
    public Connection connect(String url, Properties info) throws SQLException {
        System.out.println("准备创建数据库连接.url:" + url);
        System.out.println("JDBC配置信息:" + info);
        info.setProperty("user", "root");
        Connection connection =  super.connect(url, info);
        System.out.println("数据库连接创建完成!" + connection.toString());
        return connection;
    }

    @Override
    public Logger getParentLogger() throws SQLFeatureNotSupportedException {
        return null;
    }
}

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