Java9新特性系列(module&spi)

史培培發表於2018-02-13

Java9新特性系列(module&spi)
上兩篇已經深入分析了Java9新特性系列(深入理解模組化),以及Java9新特性系列(module&maven&starter),有讀者又提到了與模組化相關的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,敬請期待~

Java9新特性系列(module&spi)

微信公眾號:碼上論劍
請關注我的個人技術微信公眾號,訂閱更多內容

相關文章