SPI(Service Provider Interface)
从1.6引入,基于ClassLoader 来加载并发现服务的机制
对于msyql驱动
引入依赖
<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源码:
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驱动
引入依赖
<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源码:
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:
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.DriverClass.forName(aDriver, true, ClassLoader.getSystemClassLoader());}
}
在ServiceLoader里:
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时
Class.forName("com.mysql.cj.jdbc.Driver"); // 断点由此进入DriverManager静态代码块断点
Connection conn = DriverManager.getConnection("***", "root", "123456");
Class.forName() 中有一个本地方法
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时
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来发现和加载相应驱动。