什么是SPI这里不做详细介绍,我是参考了简书上的 介绍https://www.jianshu.com/p/46b42f7f593c
其中有句话说的比较简洁到位:
Java SPI 实际上是“基于接口的编程+策略模式+配置文件”组合实现的动态加载机制。
private static Connection getConnection(
String url, java.util.Properties info, Class<?> caller) throws SQLException {
/*
* When callerCl is null, we should check the application's
* (which is invoking this class indirectly)
* classloader, so that the JDBC driver class outside rt.jar
* can be loaded from here.
*/
ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
synchronized(DriverManager.class) {
// synchronize loading of the correct classloader.
if (callerCL == null) {
callerCL = Thread.currentThread().getContextClassLoader();
}
}
if(url == null) {
throw new SQLException("The url cannot be null", "08001");
}
println("DriverManager.getConnection(\"" + url + "\")");
// Walk through the loaded registeredDrivers attempting to make a connection.
// Remember the first exception that gets raised so we can reraise it.
SQLException reason = null;
// 这里体现了策略模式,不同驱动会尝试获取链接,获取成功返回;获取失败则循环策略继续获取;
// 这里有个疑问registeredDrivers是怎么初始的呢?发现有个静态方法java.sql.DriverManager#registerDriver(java.sql.Driver, java.sql.DriverAction)可以对其进行初始化,而这个方法又是哪里在调用,先留个疑问,下一步给出答案
for(DriverInfo aDriver : registeredDrivers) {
// If the caller does not have permission to load the driver then
// skip it.
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());
}
}
// if we got here nobody could connect.
if (reason != null) {
println("getConnection failed: " + reason);
throw reason;
}
println("getConnection: no suitable driver found for "+ url);
throw new SQLException("No suitable driver found for "+ url, "08001");
}
上面就是我们初学数据库连接时常有代码
Connection con = (Connection) DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:orcl", "用户名", "密码");
的源码,今天才知道这里还用了策略模式
但是这个配置文件怎么起作用的?
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方法,调用了ServiceLoader.load(Driver.class); 这里就加载了配置文件,并加载到jvm内存中
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;
}
// If the driver is packaged as a Service Provider, load it.
// Get all the drivers through the classloader
// exposed as a java.sql.Driver.class service.
// ServiceLoader.load() replaces the sun.misc.Providers()
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
// 这里只是获取了ServiceLoader实例,还没有真正的加载java.sql.Driver的实现类
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator();
/* Load these drivers, so that they can be instantiated.
* It may be the case that the driver class may not be there
* i.e. there may be a packaged driver with the service class
* as implementation of java.sql.Driver but the actual class
* may be missing. In that case a java.util.ServiceConfigurationError
* will be thrown at runtime by the VM trying to locate
* and load the service.
*
* Adding a try catch block to catch those runtime errors
* if driver not available in classpath but it's
* packaged as service and that service is there in classpath.
*/
try{
// hasNext方法会找到所有jar包下java.sql.Driver文件中的java.sql.Driver接口的实现类
while(driversIterator.hasNext()) {
// driversIterator是ServiceLoader中自定义的迭代器,调用next方法是将java.sql.Driver的实现类都会被加载到jvm内存
driversIterator.next();
}
} catch(Throwable t) {
// Do nothing
}
return null;
}
});
println("DriverManager.initialize: jdbc.drivers = " + drivers);
if (drivers == null || drivers.equals("")) {
return;
}
String[] driversList = drivers.split(":");
println("number of Drivers:" + driversList.length);
// 这里加载的是系统属性中配置的,而不是SPI机制加载的类
for (String aDriver : driversList) {
try {
println("DriverManager.Initialize: loading " + aDriver);
Class.forName(aDriver, true,
ClassLoader.getSystemClassLoader());
} catch (Exception ex) {
println("DriverManager.Initialize: load failed: " + ex);
}
}
}
至此还留有一个疑问没解答,java.sql.DriverManager#registerDriver(java.sql.Driver, java.sql.DriverAction)方法是在哪里调用初始化registeredDrivers数组列表,(registeredDrivers列表是要在在getConnection方法中循环调用的)
看mysql 对java.sql.Driver接口的 实现
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
//
// Register ourselves with the DriverManager
// 静态块中调用了java.sql.DriverManager.registerDriver方法,而静态块在加载到jvm内存时就被执行了,所以上面在调driversIterator.next()方法加载mysql 的驱动实现到jvm内存时,已经将下面这段逻辑执行了,也就初始化了registeredDrivers列表
static {
try {
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
/**
* Construct a new driver and register it with DriverManager
*
* @throws SQLException
* if a database error occurs.
*/
public Driver() throws SQLException {
// Required for Class.forName().newInstance()
}
}
第三部分贴的代码有点多,要耐心看代码看注释;
这里总结一下第三部分的总思路是: 获取ServiceLoader实例,遍历ServiceLoader中的Iterator,Iterator的hasNext会加载配置文件,Iterator的next方法会将接口的实现加载到jvm内存中,加载接口实现到jvm内存时会执行静态代码块,静态代码块会初始化registeredDrivers策略列表,registeredDrivers策略列表在getConnection时就发挥策略作用了