那么,在 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 机制中。
查看 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
位置:
其内容为:
com.mysql.jdbc.Driver
com.mysql.fabric.jdbc.FabricMySQLDriver
所以,会创建 com.mysql.jdbc.Driver 类、com.mysql.fabric.jdbc.FabricMySQLDriver 类
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
methodsgetConnection
andgetDrivers
have been enhanced to support the Java Standard EditionService 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 ofjava.sql.Driver
. For example, to load themy.sql.Driver
class,
theMETA-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 usingClass.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 为什么可以不需要注册驱动了吧。
在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());
}
}
...
}
既然我们已经明白了数据库创建的过程,那么,我们可以尝试一下,自己创建一个驱动类 MyDriver。
按照之前的概念,总结出服务接口、服务实现:
服务接口(JDK):java.sql.Driver
服务实现:com.tinady.jdbc.MyDriver
按照 Java 中的 SPI 机制规范,需要做出以下准备工作:
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;
}
}