dubbo源碼—SPI

Java中的SPI

SPI,Service Provider Interface,java中提供的一種使程序可擴展的方式,系統定義好接口規範,供其餘服務提供方實現,服務提供方將本身jar包META-INF/services下新建一個以接口全名稱定義的文件,裏面內容寫上本身服務的實現的類名,每一行表明一個實現,服務使用方能夠經過ServiceLoader.load加載全部的服務,而後判斷能夠使用的服務。具體能夠參考referencejava

dubbo中的SPI

dubbo也使用了相似Java中的SPI機制,不過服務發現等都是dubbo本身實現的。緩存

dubbo中的ServiceLoader:com.alibaba.dubbo.common.extension.ExtensionLoaderapp

dubbo中的擴展聲明配置文件的位置:ide

private static final String SERVICES_DIRECTORY = "META-INF/services/";
private static final String DUBBO_DIRECTORY = "META-INF/dubbo/";
private static final String DUBBO_INTERNAL_DIRECTORY = DUBBO_DIRECTORY + "internal/";

dubbo中的擴展聲明配置文件的內容(好比com.alibaba.dubbo.rpc.Protocol):url

filter=com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper
listener=com.alibaba.dubbo.rpc.protocol.ProtocolListenerWrapper
mock=com.alibaba.dubbo.rpc.support.MockProtocol

文件裏面都是name=value的格式,name就是該擴展的名稱,value是實現的擴展類的全限定名城,以#開始的都是註釋.net

註解

dubbo爲了支持可擴展的SPI機制,有三個相關重要的註解code

/** 
 * 寫在可擴展的接口上面,代表該接口是支持SPI擴展的,有一個屬性value表示默認的擴展類名稱(就是配置文件中的name)
 * 好比:Protocol上的@SPI("dubbo"),說明默認的Protocol的擴展是name爲dubbo的擴展,
 * 查找配置文件META_INFO/dubbo.internal/com.alibaba.dubbo.rpc.Protocol
 * 對應的擴展類是com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol
 */
com.alibaba.dubbo.common.extension.SPI

/**
 * 寫在類上面表示該類是默認的adapter,寫在方法上表示該方法須要被adapt,
 * 屬性value表示須要被adapt成的擴展的key(在url中配   置的key)
 * 好比com.alibaba.dubbo.common.threadpool.ThreadPool#getExecutor方法上面的註解:@Adaptive({Constants.THREADPOOL_KEY})
 * 表示該方法返回的Executor是url中key是threadpool指定的擴展,若是url中沒有配置則使用默認的擴展
 */
com.alibaba.dubbo.common.extension.Adaptive

/**
 * 指定默認激活的擴展,屬性group和value用來過濾(判斷何時激活)
 * 好比:com.alibaba.dubbo.rpc.filter.ExceptionFilter上的@Activate(group = Constants.PROVIDER)
 * 該filter只有在provider一側的時候纔會被激活,也就是服務提供方的時候纔會使用該filter
 */
com.alibaba.dubbo.common.extension.Activate

獲取擴展

dubbo獲取擴展的方式都是先獲取extensionLoader,而後經過loader去加載對應的擴展對象

獲取對應的extensionLoader的方法,每一個擴展接口都有本身的extensionLoader實例,獲取以後緩存在EXTENSION_LOADERS中blog

com.alibaba.dubbo.common.extension.ExtensionLoader#getExtensionLoader排序

獲取擴展的方法主要有:

// 獲取適配後的擴展
com.alibaba.dubbo.common.extension.ExtensionLoader#getAdaptiveExtension
// 獲取active的擴展
com.alibaba.dubbo.common.extension.ExtensionLoader#getActivateExtension
// 獲取默認配置的擴展
com.alibaba.dubbo.common.extension.ExtensionLoader#getDefaultExtension
// 根據name(配置文件中的key)獲取擴展
com.alibaba.dubbo.common.extension.ExtensionLoader#getExtension

加載擴展的調用路徑

com.alibaba.dubbo.common.extension.ExtensionLoader#getExtensionClasses
com.alibaba.dubbo.common.extension.ExtensionLoader#loadExtensionClasses
com.alibaba.dubbo.common.extension.ExtensionLoader#loadFile

getAdaptiveExtension

獲取指定擴展接口的adaptive類,能夠是動態生成的,也能夠使用註解@Adaptivate指定

主要步驟
  1. 先嚐試從緩存中get,沒有再去load
  2. load的時候,先判斷是否有SPI註解,若是有註解判斷value值配置是否正確(由於value指定的是默認擴展,不容許指定多個默認值),若是正確的話設置字段cachedDefaultName爲配置的默認擴展
  3. 而後從指定的三個位置(就是上面配置文件的三個位置)查找並加載全部的擴展
  4. 主要的load工做由loadFile完成
  5. 找到class以後,調用newInstance實例化
  6. 判斷該類是否有類字段是其餘擴展,若是有則從全部的擴展中查找到對應的擴展並注入到該實例中
  7. 若是第4步沒有找到配置了的adaptiveClass,須要動態生成一個adaptiveClass,調用的是createAdaptiveExtensionClassCode方法生成動態代碼
  8. 獲取classLoader,默認是加載ExtensionLoader的類加載器,用來加載動態編譯後的class
  9. 獲取com.alibaba.dubbo.common.compiler.Compiler.class的AdaptiveExtension,用來編譯動態生成的代碼
  10. 返回進過類加載器加載的動態編譯後的Class
loadFile的主要過程
  1. 查找classLoader,默認使用ExtensionLoader對應的類加載器
  2. 使用classLoader查找指定目錄下全部以擴展接口全限定名命名的配置文件,若是找到了繼續,若是沒有找到直接返回
  3. 循環加載每一個查找到的配置文件
  4. 針對每一個配置文件,讀入每一行配置,忽略空行和註釋("#")
  5. 若是類上面有Adaptive註解,設置爲cachedAdaptiveClass
  6. 判斷該擴展類是否有包含擴展接口類型的構造方法,若是有,將該class加入到cachedWrapperClasses,未來會使用wrapper包裹其餘
  7. 若是沒有上面這樣的構造方法,判斷該類是否有Activate註解,若是有則加入cachedActivates
  8. 並將配置的該class添加到extensionClasses
createAdaptiveExtensionClassCode生成動態代碼

該方法會生成一個實現了擴展接口的adaptive類,該類實現了接口的全部方法,對於沒有Adaptive註解的方法註解拋出UnsupportedOperationException,若是有該註解會從url中找到須要適配到的擴展,而後調用適配到的擴展的對應的方法。好比ProxyFactory接口的getProxy方法,默認會去url中找key爲proxy對應的value,若是沒有配置proxy的話,默認的value是javassist,而後根據該value加載對應的擴展,默認擴展JavassistProxyFactory,而後調用JavassistProxyFactory#getProxy

  1. 判斷給定擴展接口是否有被Adaptive註解的方法,若是沒有就報錯,若是有則繼續
  2. 動態拼接包名、import等代碼
  3. 循環每個方法進行進行適配adapt
  4. 若是沒有Adaptive註解,則在適配方法內部拋出UnsupportedOperationException
  5. 若是有Adaptive註解,主要找到須要使用哪個擴展來適配,下面的代碼主要在找這個擴展的key
  6. 先判斷該方法的入參有沒有URL類型的參數,若是有則使該url,
  7. 若是沒有URL類型的參數,判斷該方法的參數有沒有get方法的返回值是URL,若是沒有則報錯
  8. 若是有返回值是URL類型入參,作一些NPE的校驗,使用get方法獲取url
  9. 上面已經獲取到url,接下來是從url中獲取配置該類擴展的名稱(配置文件中name=value中的name),須要先知道url中對應的key
  10. 若是adaptive註解上配置了value屬性則直接使用做爲key
  11. 若是沒有配置,則使用擴展點接口名的點分隔做爲key
  12. 找到key以後就是在url中找到key對應的value,分如下幾種狀況:
    • key是protocol,若是有默認擴展名稱配置cachedDefaultName而且url.getProtocol()爲空則使用cachedDefaultName,不然直接經過url.getProtocol()獲取對應的擴展的name
    • key不是protocol,若是參數中沒有Invocation則直接衝url中獲取;若是有Invocation須要獲取方法級的配置(由於好比loadbalance,若是方法級有單獨的配置,須要按照方法級的配置獲取擴展)
    • 注意:只有一個配置的時候纔會考慮默認配置cachedDefaultName,若是有多個配置,以最後一個獲取到的配置爲準
  13. 取到擴展的name以後,調用ExtensionLoader#getExtension獲取擴展
  14. 調用擴展的對應方法

下面是動態生成的接口com.alibaba.dubbo.rpc.ProxyFactory的適配類

import com.alibaba.dubbo.common.extension.ExtensionLoader;
public class ProxyFactory$Adpative implements com.alibaba.dubbo.rpc.ProxyFactory {
    public com.alibaba.dubbo.rpc.Invoker getInvoker(java.lang.Object arg0, java.lang.Class arg1, com.alibaba.dubbo.common
        .URL arg2) throws com.alibaba.dubbo.rpc.RpcException {
        if (arg2 == null) throw new IllegalArgumentException("url == null");
        com.alibaba.dubbo.common.URL url = arg2;
        String extName = url.getParameter("proxy", "javassist");
        if (extName == null) throw new IllegalStateException(
                "Fail to get extension(com.alibaba.dubbo.rpc.ProxyFactory) name from url(" + url.toString() +
                ") use keys([proxy])");
        com.alibaba.dubbo.rpc.ProxyFactory extension = (com.alibaba.dubbo.rpc.ProxyFactory) ExtensionLoader.getExtensionLoader(
            com.alibaba.dubbo.rpc.ProxyFactory.class).getExtension(extName);
        return extension.getInvoker(arg0, arg1, arg2);
    }
    public java.lang.Object getProxy(com.alibaba.dubbo.rpc.Invoker arg0) throws com.alibaba.dubbo.rpc.RpcException {
        if (arg0 == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null");
        if (arg0.getUrl() == null) throw new IllegalArgumentException(
                "com.alibaba.dubbo.rpc.Invoker argument getUrl() == null");
        com.alibaba.dubbo.common.URL url = arg0.getUrl();
        String extName = url.getParameter("proxy", "javassist");
        if (extName == null) throw new IllegalStateException(
                "Fail to get extension(com.alibaba.dubbo.rpc.ProxyFactory) name from url(" + url.toString() +
                ") use keys([proxy])");
        com.alibaba.dubbo.rpc.ProxyFactory extension = (com.alibaba.dubbo.rpc.ProxyFactory) ExtensionLoader.getExtensionLoader(
            com.alibaba.dubbo.rpc.ProxyFactory.class).getExtension(extName);
        return extension.getProxy(arg0);
    }
}

getActivateExtension

該方法主要獲取兩種擴展:

  1. 符合條件的配置了@Activate註解的擴展,從cachedActivates中查找
  2. 指定name的擴展

找到的全部擴展是有順序的,若是用戶有配置按照用戶配置的前後順序(默認activate的擴展的排序規則ActivateComparator)

從cachedActivates中查找的主要步驟是:

  1. getExtensionClasses加載全部的擴展,其中會將類上面有Activate的class加入cachedActivates
  2. 若是配置了"-default",表示不使用默認有Activate註解擴展,若是沒有該配置,衝cachedActivates中查找合適的擴展眼,根據如下條件過濾默認activate的擴展,下面條件都符合纔會添加到最終的返回值中
    • 根據註解上配置的group是否和指定的一致
    • url中配置不包含該擴展,url中也不包含去除該擴展name的配置,Activate註解配置了value而且url中的配置包含該value
  3. 將從cachedActivates中找到的擴展排序

查找指定name擴展的步驟是:

  1. 配置的name不是用來排除擴展的,而且沒有沒有排除該擴展的配置
  2. 若是指定的配置中包含默認配置的"default",將default前面的配置擴展放在list的前面,default後面的配置正常放在list的後面,保證按照用戶配置的順序獲取對應的擴展

getDefaultExtension

獲取默認的擴展,也是先加載該接口全部的擴展,這個過程當中會將SPI註解配置了value——也就是默認的使用的擴展,賦值給cachedDefaultName,而後調用getExtension加載該nam對應的擴展

getExtension

根據給定的name來獲取擴展的class,返回對應的實例對象

  1. 對name判空
  2. 若是name是true則getDefaultExtension獲取默認擴展
  3. 嘗試從緩存中獲取holder,若是沒有則先添加holder
  4. double check holder中是否有對應的實例對象,沒有的話就調用createExtension建立,建立成功以後set到holder中

createExtension

根據名稱獲取擴展

  1. 加載該接口對應的全部擴展,查找該name對應擴展的class,若是沒有該name的擴展則報錯
  2. 從緩存中獲取class,若是沒有則newInstance,並加入緩存
  3. 屬性注入:若是該實例對象的字段是擴展接口類型,查找全部擴展中該擴展的並將注入到實例對象中
  4. 使用wrapper包裝:在loadFile加載擴展的時候會把符合條件(條件就是擴展類包含擴展接口做爲入參的構造方法)的wrapper加入cachedWrapperClasses,將全部的wrapper應用到instance,並調用injectExtension注入wrapper中屬性

總結

dubbo按照java中的SPI機制來保證擴展性,按照約定將對應的配置文件放在指定的目錄下,經過讀取配置件的方式來獲取接口的擴展實現,能夠使用動態編譯的方法能夠動態獲取擴展的適配類。將全部實例化的類緩存起來能夠保證單例並且加快速度,同時dubbo有簡單的屬性裝配功能,在實例化擴展的時候會將屬性也爲擴展接口的字段進行注入。

相關文章
相關標籤/搜索