JAVA SPI解析

JAVA SPI解析

在閱讀Dubbo源碼時發(fā)現(xiàn)Dubbo針對java的spi機制做了擴展。那么spi究竟是什么呢?

SPI 是什么

SPI全稱為Service Provider Interface,是Java提供的用來加載第三方實現(xiàn)的service的API。
比如java.sql.Driver接口,第三方廠商比如MySql,PostgreSql均會實現(xiàn)這個接口來提供接入數(shù)據(jù)庫的實現(xiàn),Java的SPI機制就可以某個接口尋找服務實現(xiàn)。
當服務提供者實現(xiàn)了一種接口后,需要在classpath的META-INF/services中創(chuàng)建一個以服務接口名命名的文件,文件的內(nèi)容是這個接口的具體實現(xiàn)類。當調(diào)用者需要用到這個服務時,就會查找對應jar包下的META-INF/services中的配置,如果找到了,那么就會實例化這個實現(xiàn)類,調(diào)用者就可以調(diào)用對應的服務了。Java中,查找實現(xiàn)類的工具類是java.util.ServiceLoader

一個DEMO

接下來我們自己實現(xiàn)一個簡單的SPI服務,從中可以學習到SPI的機制。


image
  • api為接口,其下有一個名為People的interface,提供一個hello的方法
    image
  • jack與jenny為People的實現(xiàn)module,各自繼承People接口,并實現(xiàn)自己的代碼邏輯,本例為了簡單只是單純的輸出了各自的名字,如下所示:
    image

    image
  • jack與jenny分別在自己module下的resources文件夾下創(chuàng)建META-INF/services文件夾,并且在該文件夾下創(chuàng)建一個名為io.wkz.People的文件,文件內(nèi)容分別為JackJenny的全限定名。其結構如下:
    image
  • service依賴上面三個module,并且在main函數(shù)中,使用ServiceLodaer加載People的實現(xiàn)類,并且分別調(diào)用hello方法,如下所示:
    image

    其輸出如下:
    image

觀察main函數(shù)會發(fā)現(xiàn),我們并沒有在main函數(shù)下實例化JackJenny,但是我們在調(diào)用服務時,仍然調(diào)用到了這兩個類的實例。從這里可以發(fā)現(xiàn),SPI的核心思想其實就是解耦。

原理解析

既然在上一個例子里我們是使用的ServiceLoader來獲取不同的服務提供者,那么我們就進入ServiceLoader源碼探索一番吧。
首先查看一下ServiceLoader的屬性字段

public final class ServiceLoader<S>
    implements Iterable<S>
{

    //看屬性名可知此字段就是查找配置文件的前綴文件夾名,從這里也了解了為什么必須要在META-INF/services下新增配置文件。
    private static final String PREFIX = "META-INF/services/";

    // 代表被加載的類或者接口
    private final Class<S> service;

    // 類加載器,用于加載并實例化服務
    private final ClassLoader loader;

    // 創(chuàng)建ServiceLoader時采用的訪問控制上下文
    private final AccessControlContext acc;

    // 實例化后的服務緩存,用的是LinkedHashMap,所以是按順序排列的,具體排列順序是按實例化的先后順序
    private LinkedHashMap<String,S> providers = new LinkedHashMap<>();

    // 迭代器,Lazy代表是懶的
    private LazyIterator lookupIterator;
    
    ...
    
}

屬性字段上比較簡單,接下來我們看一下ServiceLoader.load方法,跟蹤代碼發(fā)現(xiàn)其最終是實例化了一個ServiceLoader類并返回。繼續(xù)進入構造方法

private ServiceLoader(Class<S> svc, ClassLoader cl) {
    service = Objects.requireNonNull(svc, "Service interface cannot be null");
    loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
    acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
    reload();
}

構造函數(shù)里前面三行屬于防御式編程,將null處理掉,要么報錯,要么設置默認值。最后一行的reload方法是清空providers,并且實例化一個LazyIterator用于后期迭代。
LazyIterator也比較簡單,看名字就是實現(xiàn)了Iterator接口的。比較關鍵的方法是nextService

private S nextService() {
    //防御式編程
    if (!hasNextService())
        throw new NoSuchElementException();
    //nextName是在hasNextService方法中賦值的,這里獲取到就置空
    String cn = nextName;
    nextName = null;
    Class<?> c = null;
    try {
        //使用當前的ClassLoader加載類
        c = Class.forName(cn, false, loader);
    } catch (ClassNotFoundException x) {
        //加載失敗,拋錯。
        fail(service,
             "Provider " + cn + " not found");
    }
    //判斷加載的類是不是當前service的子類或者實現(xiàn)類。
    if (!service.isAssignableFrom(c)) {
        fail(service,
             "Provider " + cn  + " not a subtype");
    }
    try {
        //實例化類。注意這里,這步表明我們實現(xiàn)的service必須要有一個無參構造函數(shù)
        S p = service.cast(c.newInstance());
        //進行緩存。這里可以看ServiceLoader的Iterator實現(xiàn)。是先判斷的providers是不是hasNext,如果不是,則進入到本LazyIterator中,從一定程度上解決了創(chuàng)建時就加載全部實現(xiàn)類的問題,但是并沒有治本。
        providers.put(cn, p);
        return p;
    } catch (Throwable x) {
        fail(service,
             "Provider " + cn + " could not be instantiated",
             x);
    }
    //防御式編程
    throw new Error();          // This cannot happen
}

從代碼層面上看,我們可以得出幾點結論:

  1. ServiceLoader是懶加載,有緩存的。
  2. ServiceLoader加載時,全部加載,沒有提供單個service獨立加載的方法,如果調(diào)用方只希望調(diào)用部分實現(xiàn)類,目前ServiceLoader并不支持。
  3. 雖然ServiceLoader內(nèi)部使用了LinkedHashMap,但是并沒有對外暴露接口,只能通過Iterator訪問。
  4. 并發(fā)訪問同一個ServiceLoader是線程不安全的。

總結

Java提供的SPI機制,優(yōu)勢在于解耦,調(diào)用方不需要明確耦合第三方實現(xiàn)類,而是交由ServiceLoader提供。當程序需要使用另一種第三方實現(xiàn)類時,直接替換依賴就可以,無需重構代碼。
缺點其實在上面說到了。這里就不贅述了。Dubbo其實也是針對這些默認SPI的缺點才自己實現(xiàn)了Dubbo SPI。

?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容