spi
,本篇將進行分析。
SPI是什麼?
提到SPI呢,就不得不提一下API:
API:Application Programming Interface,即應用程式程式設計介面,在程式外部進行呼叫
SPI:Service Provider Interface,服務提供商介面
SPI核心思想是什麼?
我們知道,一個系統中會有很多模組,比如資料庫模組、日誌模組、排程模組、各種業務模組等等,每一類的模組都有很多種實現, 資料庫可以用mysql、oracle等,日誌可以用log4j、logback等,那麼對於不同的場景有不同的選型,如何能做到可插拔呢,那就是SPI了。
可插拔原則可以理解為系統與外掛的關係,系統提供了一些介面,第三方外掛進行實現。
面向介面程式設計,不繫結實現,為了在模組裝配的時候不在程式碼中動態去指定具體的實現,就需要去發現具體的實現,即服務發現,其實就類似於回撥,只不過回撥的時候需要找到具體的實現,spi就幫我們做了去尋找實現的工作。
這一思想在模組化設計中尤為重要。
SPI實現方式?
SPI具體的實現方式分兩種:
- 應用系統自身提供預設實現
- 第三方提供實現
SPI有什麼例子呢?
下面我們就以jdk中的jdbc模組兒進行分析:
package java.sql;
import java.util.logging.Logger;
public interface Driver {
Connection connect(String url, java.util.Properties info)
throws SQLException;
boolean acceptsURL(String url) throws SQLException;
DriverPropertyInfo[] getPropertyInfo(String url, java.util.Properties info)
throws SQLException;
int getMajorVersion();
int getMinorVersion();
boolean jdbcCompliant();
public Logger getParentLogger() throws SQLFeatureNotSupportedException;
}
複製程式碼
我們可以看到,java.sql.Driver介面定義了每個實現必須實現的一些方法,接下來我們就看具體的實現:
- mysql
META-INF/services/java.sql.Driver
com.mysql.jdbc.Driver
com.mysql.fabric.jdbc.FabricMySQLDriver
複製程式碼
在這個檔案中指定了具體的實現
- oracle
META-INF/services/java.sql.Driver
oracle.jdbc.OracleDriver
複製程式碼
在JDBC4以前,我們還需要使用比如Class.forName("com.mysql.jdbc.Driver")的方式來裝載驅動。
如上圖所示:
JDBC也基於spi的機制來發現驅動提供商了,可以通過META-INF/services/java.sql.Driver
檔案裡指定實現類的方式來暴露驅動提供者。
其中,META-INF/services/
是固定的,java.sql.Driver
為介面對應的package
,檔案中為具體的實現類。
SPI如何被框架發現呢?
框架可以使用java提供的java.util.ServiceLoader類得到SPI的實現。我們來看下java.sql.DriverManager中是如何去發現的:
private static void loadInitialDrivers() {
...
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
// 獲取具體的實現類
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;
}
});
...
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);
}
}
}
複製程式碼
使用
ServiceLoader<XXX> loadedDrivers = ServiceLoader.load(XXX.class);
,實際上就是到具體的路徑下讀取檔案內容。
SPI應用
用過阿里Dubbo
的開發者都知道,Dubbo對JDK中SPI進行了擴充套件和改進,這個在以後dubbo相關的文章中再進行介紹~
好了,本期就講到這裡,下期我們講講Java9中到另一個新工具
JShell
,敬請期待~