SPI(Service Provider Interface)機制


JAVA SPI

約定如下:當服務的提供者提供了服務接口的一種實現之后,在jar包的META-INF/services/ 目錄中同時創建一個以服務接口命名的文件,該文件中的內容就是實現該服務接口的具體實現類。

Java中提供了一個用於服務實現查找的工具類:java.util.ServiceLoader。

//將服務聲明的文件名稱定義為: example.spi.service.IService,與接口名稱一致,其中的內容包括:
//example.spi.service.PrintServiceImpl  
//example.spi.service.EchoServiceImpl  

public static void main(String[] args) {  
    //實例化具體類時需要注意對應類有無參構造函數
    ServiceLoader<Service> serviceLoader = ServiceLoader.load(IService.class);  
   
    for (IService service : serviceLoader) {  
        service.printInfo();  
    }  
}  

ServiceLoader的源碼分析

重要屬性:

// 加載的接口
private Class<S> service;

// 用於緩存已經加載的接口實現類,其中key為實現類的完整類名
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();

// 用於延遲加載接口的實現類
private LazyIterator lookupIterator;

第一步:獲取一個ServiceLoader<Service> serviceLoader = ServiceLoader.load(IService.class);實例,此時還沒有進行任何接口實現類的加載操作,屬於延遲加載類型的。只是創建了LazyIterator lookupIterator對象而已。

第二步:ServiceLoader實現了Iterable接口,即實現了該接口的iterator()方法,實現內容如下:

    // for循環遍歷ServiceLoader的過程其實就是調用上述hasNext()和next()方法的過程
    public Iterator<S> iterator() {
        return new Iterator<S>() {

            Iterator<Map.Entry<String,S>> knownProviders
                = providers.entrySet().iterator();

            public boolean hasNext() {
                if (knownProviders.hasNext())
                    return true;
                // 第一次循環遍歷會使用lookupIterator去查找,之后就緩存到providers中。
          // LazyIterator會去加載類路徑下/META-INF/services/接口全稱 文件的url地址,文件加載並解析完成之后,得到一系列的接口實現類的完整類名。
          // 調用next()方法時才回去真正執行接口實現類的加載操作,並根據無參構造器創建出一個實例,存到providers中。
return lookupIterator.hasNext(); } public S next() { //再次遍歷ServiceLoader,就直接遍歷providers中的數據 if (knownProviders.hasNext()) return knownProviders.next().getValue(); return lookupIterator.next(); } public void remove() { throw new UnsupportedOperationException(); } }; }

ServiceLoader缺點

  1. 雖然ServiceLoader使用延遲加載,但是基本只能通過遍歷全部獲取,也就是接口的實現類全部加載並實例化一遍。如果你並不想用某些實現類,它也被加載並實例化了,這就造成了浪費。
  2. 獲取某個實現類的方式不夠靈活,只能通過Iterator形式獲取,不能根據某個參數來獲取對應的實現類

Dubbo SPI

 dubbo的擴展機制與JAVA SPI比較類似,但額外增加了其他功能:

  1. 可以根據接口名稱來獲取服務,dubbo spi 可以通過getExtension(String key)的方法方便的獲取某一個想要的擴展實現,java的SPI機制需要加載全部的實現類。
  2. 服務聲明文件支持A=B的方式,此時A為名稱B為實現類。 文件名:com.alibaba.dubbo.rpc.Filter
    • echo=com.alibaba.dubbo.rpc.filter.EchoFilter

    • generic=com.alibaba.dubbo.rpc.filter.GenericFilter

    • genericimpl=com.alibaba.dubbo.rpc.filter.GenericImplFilter

  3. 支持擴展IOC依賴注入功能,可以為Service之間的依賴關系注入相關的服務並保證單例。
    • 舉例來說:接口A,實現者A1、A2。接口B,實現者B1、B2。

      現在實現者A1含有setB()方法,會自動注入一個接口B的實現者,此時注入B1還是B2呢?

      都不是,而是注入一個動態生成的接口B的實現者B$Adpative,該實現能夠根據參數的不同,自動引用B1或者B2來完成相應的功能。

      Protocol$Adpative是根據URL參數中protocol屬性的值來選擇具體的實現類的。

      如值為dubbo,則從ExtensionLoader<Protocol>中獲取dubbo對應的實例,即DubboProtocol實例

      如值為hessian,則從ExtensionLoader<Protocol>中獲取hessian對應的實例,即HessianProtocol實例

      也就是說Protocol$Adpative能夠根據url中的protocol屬性值動態的采用對應的實現。

  4. 對擴展采用裝飾器模式進行功能增強,類似AOP實現的功能

    • 接口A的另一個實現者AWrapper1。在獲取某一個接口A的實現者A1的時候,已經自動被AWrapper1包裝了。
      private A a;
      AWrapper1(A a){
          this.a=a;
      }

ExtensionLoader源碼分析 

ExtensionLoader<Protocol> protocolLoader = ExtensionLoader.getExtensionLoader(Protocol.class);
Protocol protocol = protocolLoader.getAdaptiveExtension();

@Extension("dubbo")
public interface Protocol {

    int getDefaultPort();
    @Adaptive
    <T> Exporter<T> export(Invoker<T> invoker) throws RpcException;
    @Adaptive
    <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException;
    void destroy();
}

第一步:根據要加載的接口創建出一個ExtensionLoader實例

重要屬性: ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<Class<?>, ExtensionLoader<?>>();

用於緩存所有的擴展加載實例,這里加載Protocol.class,就以Protocol.class為key,創建的ExtensionLoader為value存儲到上述EXTENSION_LOADERS中。

第二步:ExtensionLoader實例是加載Protocol的實現類

  1. 先解析Protocol上的Extension注解的name,存至String cachedDefaultName屬性中,作為默認的實現
  2. 到類路徑下的加載 META-INF/services/com.alibaba.dubbo.rpc.Protocol文件,然后就是讀取每一行內容,加載對應的class。(擴展配置文件: /META-INF/dubbo/internal,/META-INF/dubbo/,META-INF/services)
  3. 上述class分成三種情況來處理,對於一個接口的實現者,ExtensionLoader分三種情況來分別存儲對應的實現者,屬性分別如下:Class<?> cachedAdaptiveClass;Set<Class<?>> cachedWrapperClasses;Reference<Map<String, Class<?>>> cachedClasses;
    • 情況1: 如果這個class含有Adaptive注解,則將這個class設置為Class<?> cachedAdaptiveClass。
    • 情況2: 嘗試獲取對應接口參數的構造器,如果能夠獲取到,則說明這個class是一個裝飾類即需要存到Set<Class<?>> cachedWrapperClasses中
    • 情況3: 如果沒有上述構造器。則獲取class上的Extension注解,根據該注解的定義的name作為key,存至Reference<Map<String, Class<?>>> cachedClasses結構中 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM