SPI 技術
SPI全稱Service Provider Interface,是Java提供的一套用來被第三方實現或者擴展的API,它可以用來啓用框架擴展和替換組件。
Java SPI 實際上是“基於接口的編程+策略模式+配置文件”組合實現的動態加載機制。
實現方式:
當服務的提供者提供了一種接口的實現之後,需要在 Classpath 下的 META-INF/services/ 目錄裏創建一個以服務接口命名的文件,此文件記錄了該 jar 包提供的服務接口的具體實現類。當某個應用引入了該 jar 包且需要使用該服務時,JDK SPI 機制就可以通過查找這個 jar 包的 META-INF/services/ 中的配置文件來獲得具體的實現類名,進行實現類的加載和實例化,最終使用該實現類完成業務功能。
樣例
抽象接口類:
public interface Log {
public void log(String log);
}
兩個實現類:
public class Log4j implements Log {
@Override
public void log(String log) {
System.out.println("log4j:" + log);
}
}
public class Logback implements Log {
@Override
public void log(String log) {
System.out.println("logback " + log);
}
}
測試類:
public class SpiTestMain {
public static void main(String[] args) {
ServiceLoader<Log> serviceLoader =
ServiceLoader.load(Log.class);
Iterator<Log> iterator = serviceLoader.iterator();
while (iterator.hasNext()) {
Log log = iterator.next();
log.log("JDK SPI");
}
}
}
在項目的 resources/META-INF/services 目錄下添加的文件以及內容
運行結果:
源碼分析
JDBC加載不同數據庫廠商的驅動
以我使用的mysql-connector-java-8.0.20.jar爲例,在jar 包中的 META-INF/services 目錄下,有一個 java.sql.Driver 文件中只有一行內容
com.mysql.cj.jdbc.Driver
如果我們手動加載JDBC通常會寫這樣的代碼:
String url = "jdbc:xxx://xxx:xxx/xxx";
Connection conn = DriverManager.getConnection(url, username, pwd);
DriverManager 是 JDK 提供的數據庫驅動管理器,它的靜態代碼塊:
static {
loadInitialDrivers();
println("JDBC DriverManager initialized");
}
在調用 getConnection() 方法的時候,DriverManager 類會被 Java 虛擬機加載、解析並觸發 static 代碼塊的執行
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() {
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator();
try{
while(driversIterator.hasNext()) {
//該地方雖然只是調用了next,但是卻是會獲取到Driver的實現類,觸發實現類的初始化
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);
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);
}
}
}
注意:driversIterator.next();該代碼雖然只是調用了next,但是卻是會獲取到Driver的實現類,觸發實現類的初始化,以配置文件配置的com.mysql.cj.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!");
}
}
}
可見,靜態代碼塊向DriverManager註冊了自己。