SPI(Service Provider Interface)
从1.6引入,基于ClassLoader 来加载并发现服务的机制
对于msyql驱动
引入依赖
xml
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.27</version>
</dependency>
在mysql:mysql-connector-java:8.0.27.jar中,可以找到META-INF/services/java.sql.Driver文件:
com.mysql.cj.jdbc.Driver
找到com.mysql.cj.jdbc.Driver源码:
java
package com.mysql.cj.jdbc;
import java.sql.DriverManager;
import java.sql.SQLException;
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!");
}
}
}
- 它实现了java.sql.Driver
- 静态代码块中,向DriverManager注册了自己。
对于oracle驱动
引入依赖
xml
<dependency>
<groupId>com.hynnet</groupId>
<artifactId>oracle-driver-ojdbc6</artifactId>
<version>12.1.0.1</version>
</dependency>
在com.hynnet:oracle-driver-ojdbc6-12.1.0.1.jar中,可以找到META-INF/services/java.sql.Driver文件:
oracle.jdbc.OracleDriver
找到oracle.jdbc.OracleDriver源码:
java
public class OracleDriver implements Driver {
//
static {
try {
if (defaultDriver == null) {
defaultDriver = new oracle.jdbc.OracleDriver();
DriverManager.registerDriver(defaultDriver);
}
} catch // ...
}
}
- 它实现了java.sql.Driver
- 静态代码块中,向DriverManager注册了自己。
从DriverManager跟踪源码
对于 DriverManager:
java
public class DriverManager {
static {
loadInitialDrivers();
println("JDBC DriverManager initialized");
}
private static void loadInitialDrivers() {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator();
while(driversIterator.hasNext()) { // 断点进到这里
driversIterator.next();
}
}
});
// 最后还是根据拿到了从META-INF/services/java.sql.Driver里的Driver,如com.mysql.cj.jdbc.Driver
Class.forName(aDriver, true, ClassLoader.getSystemClassLoader());
}
}
在ServiceLoader里:
java
public final class ServiceLoader<S> implements Iterable<S> {
private static final String PREFIX = "META-INF/services/";
public boolean hasNext() { // 进入driversIterator.hasNext() 后追到这里
if (acc == null) {
return hasNextService();
}
}
private boolean hasNextService() {
String fullName = PREFIX + service.getName();
configs = ClassLoader.getSystemResources(fullName); // 断点没有进
configs = loader.getResources(fullName); // 断点进到这里
}
}
是在某个时机,触发DriverManager静态代码块,且只会执行这一次,紧接着一系列调用:
--> loadInitialDrivers()
--> ServiceLoader.load()、driversIterator.hasNext()
--> hasNextService()
--> loader.getResources(fullName)
使用classLoader.getResources() 加载所有包下"META-INF/services/java.sql.Driver"文件
获取连接测试
当使用Class.forName时
java
Class.forName("com.mysql.cj.jdbc.Driver"); // 断点由此进入DriverManager静态代码块断点
Connection conn = DriverManager.getConnection("***", "root", "123456");
Class.forName() 中有一个本地方法
java
private static native Class<?> forName0(String name, boolean initialize, ClassLoader loader, Class<?> caller) throws ClassNotFoundException;
经过这个本地方法后,断点进入DriverManager静态代码块断点,本地方法里做了啥? 如何会触发DriverManager的类加载,不知道。。
但DriverManager静态代码确实只会执行一次,即便再次使用 Class.forName("com.mysql.cj.jdbc.Driver")时, 不会进入DriverManager静态代码块断点了
当不使用Class.forName,而直接DriverManager.getConnection时
java
Connection conn = DriverManager.getConnection("***", "root", "123456"); // 断点由此进入DriverManager静态代码块断点
getConnection() 是静态方法,会触发执行DriverManager静态代码块,接着也是一系列的调用,加载所有包下"META-INF/services/java.sql.Driver"文件。
总结
通过 DriverManager、ServiceLoader,在第一次获取连接前,总会去执行一次loader.getResources(fullName),加载所有包下"META-INF/services/java.sql.Driver"文件中指定的类。
过程都由DriverManager、ServiceLoader设计好,获取驱动包名的目录"META-INF/services"也有ServiceLoader定义为常量。
所以实现一个驱动,需要按照约定,在META-INF/services/java.sql.Driver中准备好驱动所在的全
限定名。便会有ClassLoader来发现和加载相应驱动。