dubbo源碼解析(二)Dubbo擴展機制SPI

Dubbo擴展機制SPI

前一篇文章《dubbo源碼解析(一)Hello,Dubbo》是對dubbo整個項目大致的介紹,而從這篇文章開始,我將會從源碼來解讀dubbo再各個模塊的實現原理以及特色,因爲所有由截圖的方式去解讀源碼會致使文章很雜亂,因此我只會放部分截圖,所有的解讀會同步更新在我github上fork的dubbo源碼中,同時我也會在文章一些關鍵的地方加上超連接,方便讀者快速查閱。html

我會在以後的每篇文章前都寫一個目標,爲了讓讀者一眼就能知道本文是不是你須要尋找的資料。java


目標:讓讀者知道JDK的SPI思想,dubbo的SPI思想,dubbo擴展機制SPI的原理,可以讀懂實現擴展機制的源碼。

第一篇源碼分析的文章就先來說講dubbo擴展機制spi的原理,瀏覽過dubbo官方文檔的朋友確定知道,dubbo有大量的spi擴展實現,包括協議擴展、調用攔截擴展、路由擴展等26個擴展,而且spi機制運用到了各個模塊設計中。因此我打算先講解dubbo的擴展機制spi。git

JDK的SPI思想

SPI的全名爲Service Provider Interface,面向對象的設計裏面,模塊之間推薦基於接口編程,而不是對實現類進行硬編碼,這樣作也是爲了模塊設計的可拔插原則。爲了在模塊裝配的時候不在程序裏指明是哪一個實現,就須要一種服務發現的機制,jdk的spi就是爲某個接口尋找服務實現。jdk提供了服務實現查找的工具類:java.util.ServiceLoader,它會去加載META-INF/service/目錄下的配置文件。具體的內部實現邏輯爲這裏先不展開,主要仍是講解dubbo關於spi的實現原理。github

Dubbo的SPI擴展機制原理

dubbo本身實現了一套SPI機制,改進了JDK標準的SPI機制:apache

  1. JDK標準的SPI只能經過遍從來查找擴展點和實例化,有可能致使一次性加載全部的擴展點,若是不是全部的擴展點都被用到,就會致使資源的浪費。dubbo每一個擴展點都有多種實現,例如com.alibaba.dubbo.rpc.Protocol接口有InjvmProtocol、DubboProtocol、RmiProtocol、HttpProtocol、HessianProtocol等實現,若是隻是用到其中一個實現,但是加載了所有的實現,會致使資源的浪費。
  2. 把配置文件中擴展實現的格式修改,例如META-INF/dubbo/com.xxx.Protocol裏的com.foo.XxxProtocol格式改成了xxx = com.foo.XxxProtocol這種以鍵值對的形式,這樣作的目的是爲了讓咱們更容易的定位到問題,好比因爲第三方庫不存在,沒法初始化,致使沒法加載擴展名(「A」),當用戶配置使用A時,dubbo就會報沒法加載擴展名的錯誤,而不是報哪些擴展名的實現加載失敗以及錯誤緣由,這是由於原來的配置格式沒有把擴展名的id記錄,致使dubbo沒法拋出較爲精準的異常,這會加大排查問題的難度。因此改爲key-value的形式來進行配置。
  3. dubbo的SPI機制增長了對IOC、AOP的支持,一個擴展點能夠直接經過setter注入到其餘擴展點。

咱們先來看看SPI擴展機制實現的結構目錄:編程

extension目錄

(一)註解@SPI

在某個接口上加上@SPI註解後,代表該接口爲可擴展接口。我用協議擴展接口Protocol來舉例子,若是使用者在<dubbo:protocol />、<dubbo:service />、<dubbo:reference />都沒有指定protocol屬性的話,那麼就會默認DubboProtocol就是接口Protocol,由於在Protocol上有@SPI("dubbo")註解。而這個protocol屬性值或者默認值會被看成該接口的實現類中的一個key,dubbo會去META-INFdubbointernalcom.alibaba.dubbo.rpc.Protocol文件中找該key對應的value,看下圖:segmentfault

protocol的配置

value就是該Protocol接口的實現類DubboProtocol,這樣就作到了SPI擴展。數組

(二)註解@Adaptive

該註解爲了保證dubbo在內部調用具體實現的時候不是硬編碼來指定引用哪一個實現,也就是爲了適配一個接口的多種實現,這樣作符合模塊接口設計的可插拔原則,也增長了整個框架的靈活性,<u>該註解也實現了擴展點自動裝配的特性</u>。緩存

dubbo提供了兩種方式來實現接口的適配器:微信

  1. 在實現類上面加上@Adaptive註解,代表該實現類是該接口的適配器。

    舉個例子dubbo中的ExtensionFactory接口就有一個實現類AdaptiveExtensionFactory,加了@Adaptive註解,AdaptiveExtensionFactory就不提供具體業務支持,用來適配ExtensionFactory的SpiExtensionFactory和SpringExtensionFactory這兩種實現。AdaptiveExtensionFactory會根據在運行時的一些狀態來選擇具體調用ExtensionFactory的哪一個實現,具體的選擇能夠看下文Adaptive的代碼解析。

  2. 在接口方法上加@Adaptive註解,dubbo會動態生成適配器類。

    咱們從Transporter接口的源碼來解釋這種方法:

Transporter源碼

咱們能夠看到在這個接口的bind和connect方法上都有@Adaptive註解,有該註解的方法的參數必須包含URL,ExtensionLoader會經過createAdaptiveExtensionClassCode方法動態生成一個Transporter$Adaptive類,生成的代碼以下:

package com.alibaba.dubbo.remoting;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
public class Transporter$Adaptive implements com.alibaba.dubbo.remoting.Transporter{
    
    public com.alibaba.dubbo.remoting.Client connect(com.alibaba.dubbo.common.URL arg0, com.alibaba.dubbo.remoting.ChannelHandler arg1) throws com.alibaba.dubbo.remoting.RemotingException {
        //URL參數爲空則拋出異常。
        if (arg0 == null) 
            throw new IllegalArgumentException("url == null");
        
        com.alibaba.dubbo.common.URL url = arg0;
        //這裏的getParameter方法能夠在源碼中具體查看
        String extName = url.getParameter("client", url.getParameter("transporter", "netty"));
        if(extName == null) 
            throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.remoting.Transporter) name from url(" + url.toString() + ") use keys([client, transporter])");
        //這裏我在後面會有詳細介紹
        com.alibaba.dubbo.remoting.Transporter extension = (com.alibaba.dubbo.remoting.Transporter)ExtensionLoader.getExtensionLoader
        
        (com.alibaba.dubbo.remoting.Transporter.class).getExtension(extName);
        return extension.connect(arg0, arg1);
    }
    public com.alibaba.dubbo.remoting.Server bind(com.alibaba.dubbo.common.URL arg0, com.alibaba.dubbo.remoting.ChannelHandler arg1) throws com.alibaba.dubbo.remoting.RemotingException {
        if (arg0 == null) 
            throw new IllegalArgumentException("url == null");
        com.alibaba.dubbo.common.URL url = arg0;
        String extName = url.getParameter("server", url.getParameter("transporter", "netty"));
        if(extName == null) 
            throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.remoting.Transporter) name from url(" + url.toString() + ") use keys([server, transporter])");
        com.alibaba.dubbo.remoting.Transporter extension = (com.alibaba.dubbo.remoting.Transporter)ExtensionLoader.getExtensionLoader
        (com.alibaba.dubbo.remoting.Transporter.class).getExtension(extName);
        
        return extension.bind(arg0, arg1);
    }
}

能夠看到該類的兩個方法就是Transporter接口中有註解的兩個方法,我來解釋一下第一個方法connect:

  1. 全部擴展點都經過傳遞URL攜帶配置信息,因此適配器中的方法必須攜帶URL參數,才能根據URL中的配置來選擇對應的擴展實現。
  2. @Adaptive註解中有一些key值,好比connect方法的註解中有兩個key,分別爲「client」和「transporter」,URL會首先去取client對應的value來做爲我上述(一)註解@SPI中寫到的key值,若是爲空,則去取transporter對應的value,若是仍是爲空,則會根據SPI默認的key,也就是netty去調用擴展的實現類,若是@SPI沒有設定默認值,則會拋出IllegalStateException異常。

這樣就比較清楚這個適配器如何去選擇哪一個實現類做爲本次須要調用的類,這裏最關鍵的仍是強調了dubbo以URL爲總線,運行過程當中全部的狀態數據信息均可以經過URL來獲取,好比當前系統採用什麼序列化,採用什麼通訊,採用什麼負載均衡等信息,都是經過URL的參數來呈現的,因此在框架運行過程當中,運行到某個階段須要相應的數據,均可以經過對應的Key從URL的參數列表中獲取。

(三)註解@Activate

擴展點自動激活加載的註解,就是用條件來控制該擴展點實現是否被自動激活加載,在擴展實現類上面使用,<u>實現了擴展點自動激活的特性</u>,它能夠設置兩個參數,分別是group和value。具體的介紹能夠參照官方文檔。

擴展點自動激活地址: http://dubbo.apache.org/zh-cn...

(四)接口ExtensionFactory

先來看看它的源碼:

ExtensionFactory源碼

該接口是擴展工廠接口類,它自己也是一個擴展接口,有SPI的註解。該工廠接口提供的就是獲取實現類的實例,它也有兩種擴展實現,分別是SpiExtensionFactory和SpringExtensionFactory表明着兩種不一樣方式去獲取實例。而具體選擇哪一種方式去獲取實現類的實例,則在適配器AdaptiveExtensionFactory中制定了規則。具體規則看下面的源碼解析。

(五)ExtensionLoader

該類是擴展加載器,這是dubbo實現SPI擴展機制等核心,幾乎全部實現的邏輯都被封裝在ExtensionLoader中。

詳細代碼註釋見github: https://github.com/CrazyHZM/i...
  1. 屬性(選取關鍵屬性進行展開講解,其他見github註釋)
    1. 關於存放配置文件的路徑變量:

      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/";

      "META-INF/services/"、"META-INF/dubbo/"、"META-INF/dubbo/internal/"三個值,都是dubbo尋找擴展實現類的配置文件存放路徑,也就是我在上述(一)註解@SPI中講到的以接口全限定名命名的配置文件存放的路徑。區別在於"META-INF/services/"是dubbo爲了兼容jdk的SPI擴展機制思想而設存在的,"META-INF/dubbo/internal/"是dubbo內部提供的擴展的配置文件路徑,而"META-INF/dubbo/"是爲了給用戶自定義的擴展實現配置文件存放。

    2. 擴展加載器集合,key爲擴展接口,例如Protocol等:

      private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<Class<?>, ExtensionLoader<?>>();
    3. 擴展實現類集合,key爲擴展實現類,value爲擴展對象,例如key爲Class<DubboProtocol>,value爲DubboProtocol對象

      private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<Class<?>, Object>();
    4. 如下屬性都是cache開頭的,都是出於性能和資源的優化,才作的緩存,讀取擴展配置後,會先進行緩存,等到真正須要用到某個實現時,再對該實現類的對象進行初始化,而後對該對象也進行緩存。

      //如下提到的擴展名就是在配置文件中的key值,相似於「dubbo」等
      
          //緩存的擴展名與拓展類映射,和cachedClasses的key和value對換。
          private final ConcurrentMap<Class<?>, String> cachedNames = new ConcurrentHashMap<Class<?>, String>();
          //緩存的擴展實現類集合
          private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<Map<String, Class<?>>>();
          //擴展名與加有@Activate的自動激活類的映射
          private final Map<String, Activate> cachedActivates = new ConcurrentHashMap<String, Activate>();
          //緩存的擴展對象集合,key爲擴展名,value爲擴展對象
          //例如Protocol擴展,key爲dubbo,value爲DubboProcotol
          private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<String, Holder<Object
          //緩存的自適應( Adaptive )擴展對象,例如例如AdaptiveExtensionFactory類的對象
          private final Holder<Object> cachedAdaptiveInstance = new Holder<Object>();
          //緩存的自適應擴展對象的類,例如AdaptiveExtensionFactory類
          private volatile Class<?> cachedAdaptiveClass = null;
          //緩存的默認擴展名,就是@SPI中設置的值
          private String cachedDefaultName;
          //建立cachedAdaptiveInstance異常
          private volatile Throwable createAdaptiveInstanceError;
          //拓展Wrapper實現類集合
          private Set<Class<?>> cachedWrapperClasses;
          //拓展名與加載對應拓展類發生的異常的映射
          private Map<String, IllegalStateException> exceptions = new ConcurrentHashMap<String, IllegalStateException>();

      這裏提到了Wrapper類的概念。那我就解釋一下:Wrapper類也實現了擴展接口,可是Wrapper類的用途是ExtensionLoader 返回擴展點時,包裝在真正的擴展點實現外,<u>這實現了擴展點自動包裝的特性</u>。通俗點說,就是一個接口有不少的實現類,這些實現類會有一些公共的邏輯,若是在每一個實現類寫一遍這個公共邏輯,那麼代碼就會重複,因此增長了這個Wrapper類來包裝,把公共邏輯寫到Wrapper類中,有點相似AOP切面編程思想。這部分解釋也能夠結合官方文檔:

      擴展點自動包裝的特性地址: http://dubbo.apache.org/zh-cn...
  2. getExtensionLoader(Class<T> type):根據擴展點接口來得到擴展加載器。
    public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
            //擴展點接口爲空,拋出異常
            if (type == null)
                throw new IllegalArgumentException("Extension type == null");
            //判斷type是不是一個接口類
            if (!type.isInterface()) {
                throw new IllegalArgumentException("Extension type(" + type + ") is not interface!");
            }
            //判斷是否爲可擴展的接口
            if (!withExtensionAnnotation(type)) {
                throw new IllegalArgumentException("Extension type(" + type +
                            ") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");
            }
    
            //從擴展加載器集合中取出擴展接口對應的擴展加載器
            ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
    
            //若是爲空,則建立該擴展接口的擴展加載器,而且添加到EXTENSION_LOADERS
            if (loader == null) {
               EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
                    loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
            }
            return loader;
        }

    這個方法的源碼解析看上面,解讀起來仍是沒有太多難點的。就是把幾個屬性的含義弄清楚就行了。

  3. getActivateExtension方法:得到符合自動激活條件的擴展實現類對象集合
    public List<T> getActivateExtension(URL url, String key) {
            return getActivateExtension(url, key, null);
        }
        //棄用
        public List<T> getActivateExtension(URL url, String[] values) {
            return getActivateExtension(url, values, null);
        }
    
        public List<T> getActivateExtension(URL url, String key, String group) {
            String value = url.getParameter(key);
            // 得到符合自動激活條件的拓展對象數組
            return getActivateExtension(url, value == null || value.length() == 0 ? null : Constants.COMMA_SPLIT_PATTERN.split(value), group);
        }
    
        public List<T> getActivateExtension(URL url, String[] values, String group) {
            List<T> exts = new ArrayList<T>();
            List<String> names = values == null ? new ArrayList<String>(0) : Arrays.asList(values);
    
            //判斷不存在配置 `"-name"` 。
            //例如,<dubbo:service filter="-default" /> ,表明移除全部默認過濾器。
            if (!names.contains(Constants.REMOVE_VALUE_PREFIX + Constants.DEFAULT_KEY)) {
    
                //得到擴展實現類數組,把擴展實現類放到cachedClasses中
                getExtensionClasses();
                for (Map.Entry<String, Activate> entry : cachedActivates.entrySet()) {
                    String name = entry.getKey();
                    Activate activate = entry.getValue();
                    //判斷group值是否存在全部自動激活類中group組中,匹配分組
                    if (isMatchGroup(group, activate.group())) {
                        //經過擴展名得到拓展對象
                        T ext = getExtension(name);
                        //不包含在自定義配置裏。若是包含,會在下面的代碼處理。
                        //判斷是否配置移除。例如 <dubbo:service filter="-monitor" />,則 MonitorFilter 會被移除
                        //判斷是否激活
                        if (!names.contains(name)
                                && !names.contains(Constants.REMOVE_VALUE_PREFIX + name)
                                && isActive(activate, url)) {
                            exts.add(ext);
                        }
                    }
                }
                //排序
                Collections.sort(exts, ActivateComparator.COMPARATOR);
            }
            List<T> usrs = new ArrayList<T>();
            for (int i = 0; i < names.size(); i++) {
                String name = names.get(i);
                //仍是判斷是不是被移除的配置
                if (!name.startsWith(Constants.REMOVE_VALUE_PREFIX)
                        && !names.contains(Constants.REMOVE_VALUE_PREFIX + name)) {
                    //在配置中把自定義的配置放在自動激活的擴展對象前面,可讓自定義的配置先加載
                    //例如,<dubbo:service filter="demo,default,demo2" /> ,則 DemoFilter 就會放在默認的過濾器前面。
                    if (Constants.DEFAULT_KEY.equals(name)) {
                        if (!usrs.isEmpty()) {
                            exts.addAll(0, usrs);
                            usrs.clear();
                        }
                    } else {
                        T ext = getExtension(name);
                        usrs.add(ext);
                    }
                }
            }
            if (!usrs.isEmpty()) {
                exts.addAll(usrs);
            }
            return exts;
        }

    能夠看到getActivateExtension重載了四個方法,其實最終的實現都是在最後一個重載方法,由於自動激活類的條件能夠分爲無條件、只有value以及有group和value三種,具體的能夠回顧上述(三)註解@Activate

    最後一個getActivateExtension方法有幾個關鍵點:

    1. group的值合法判斷,由於group可選"provider"或"consumer"。
    2. 判斷該配置是否被移除。
    3. 若是有自定義配置,而且須要放在自動激活擴展實現對象加載前,那麼須要先存放自定義配置。
  4. getExtension方法: 得到經過擴展名得到擴展對象
    @SuppressWarnings("unchecked")
        public T getExtension(String name) {
            if (name == null || name.length() == 0)
                throw new IllegalArgumentException("Extension name == null");
            //查找默認的擴展實現,也就是@SPI中的默認值做爲key
            if ("true".equals(name)) {
                return getDefaultExtension();
            }
            //緩存中獲取對應的擴展對象
            Holder<Object> holder = cachedInstances.get(name);
            if (holder == null) {
                cachedInstances.putIfAbsent(name, new Holder<Object>());
                holder = cachedInstances.get(name);
            }
            Object instance = holder.get();
            if (instance == null) {
                synchronized (holder) {
                    instance = holder.get();
                    if (instance == null) {
                        //經過擴展名建立接口實現類的對象
                        instance = createExtension(name);
                        //把建立的擴展對象放入緩存
                        holder.set(instance);
                    }
                }
            }
            return (T) instance;
        }

    這個方法中涉及到getDefaultExtension方法和createExtension方法,會在後面講到。其餘邏輯比較簡單,就是從緩存中取,若是沒有,就建立,而後放入緩存。

  5. getDefaultExtension方法:查找默認的擴展實現
    public T getDefaultExtension() {
            //得到擴展接口的實現類數組
            getExtensionClasses();
            if (null == cachedDefaultName || cachedDefaultName.length() == 0
                    || "true".equals(cachedDefaultName)) {
                return null;
            }
            //又從新去調用了getExtension
            return getExtension(cachedDefaultName);
        }

    這裏涉及到getExtensionClasses方法,會在後面講到。得到默認的擴展實現類對象就是經過緩存中默認的擴展名去得到實現類對象。

  6. addExtension方法:擴展接口的實現類
    public void addExtension(String name, Class<?> clazz) {
            getExtensionClasses(); // load classes
    
            //該類是不是接口的自己或子類
            if (!type.isAssignableFrom(clazz)) {
                throw new IllegalStateException("Input type " +
                        clazz + "not implement Extension " + type);
            }
            //該類是否被激活
            if (clazz.isInterface()) {
                throw new IllegalStateException("Input type " +
                        clazz + "can not be interface!");
            }
    
            //判斷是否爲適配器
            if (!clazz.isAnnotationPresent(Adaptive.class)) {
                if (StringUtils.isBlank(name)) {
                    throw new IllegalStateException("Extension name is blank (Extension " + type + ")!");
                }
                if (cachedClasses.get().containsKey(name)) {
                    throw new IllegalStateException("Extension name " +
                            name + " already existed(Extension " + type + ")!");
                }
    
                //把擴展名和擴展接口的實現類放入緩存
                cachedNames.put(clazz, name);
                cachedClasses.get().put(name, clazz);
            } else {
                if (cachedAdaptiveClass != null) {
                    throw new IllegalStateException("Adaptive Extension already existed(Extension " + type + ")!");
                }
    
                cachedAdaptiveClass = clazz;
            }
        }
  7. getAdaptiveExtension方法:得到自適應擴展對象,也就是接口的適配器對象
    @SuppressWarnings("unchecked")
        public T getAdaptiveExtension() {
            Object instance = cachedAdaptiveInstance.get();
            if (instance == null) {
                if (createAdaptiveInstanceError == null) {
                    synchronized (cachedAdaptiveInstance) {
                        instance = cachedAdaptiveInstance.get();
                        if (instance == null) {
                            try {
                                //建立適配器對象
                                instance = createAdaptiveExtension();
                                cachedAdaptiveInstance.set(instance);
                            } catch (Throwable t) {
                                createAdaptiveInstanceError = t;
                                throw new IllegalStateException("fail to create adaptive instance: " + t.toString(), t);
                            }
                        }
                    }
                } else {
                    throw new IllegalStateException("fail to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError);
                }
            }
    
            return (T) instance;
        }

    思路就是先從緩存中取適配器類的對象,若是沒有,則建立一個適配器對象,而後放入緩存,createAdaptiveExtension方法解釋在後面給出。

  8. createExtension方法:經過擴展名建立擴展接口實現類的對象
    @SuppressWarnings("unchecked")
        private T createExtension(String name) {
            //得到擴展名對應的擴展實現類
            Class<?> clazz = getExtensionClasses().get(name);
            if (clazz == null) {
                throw findException(name);
            }
            try {
                //看緩存中是否有該類的對象
                T instance = (T) EXTENSION_INSTANCES.get(clazz);
                if (instance == null) {
                    EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
                    instance = (T) EXTENSION_INSTANCES.get(clazz);
                }
                //向對象中注入依賴的屬性(自動裝配)
                injectExtension(instance);
                //建立 Wrapper 擴展對象(自動包裝)
                Set<Class<?>> wrapperClasses = cachedWrapperClasses;
                if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
                    for (Class<?> wrapperClass : wrapperClasses) {
                        instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
                    }
                }
                return instance;
            } catch (Throwable t) {
                throw new IllegalStateException("Extension instance(name: " + name + ", class: " +
                        type + ")  could not be instantiated: " + t.getMessage(), t);
            }
        }

    這裏運用到了兩個擴展點的特性,分別是自動裝配和自動包裝。injectExtension方法解析在下面給出。

  9. injectExtension方法:向建立的拓展注入其依賴的屬性
    private T injectExtension(T instance) {
            try {
                if (objectFactory != null) {
                    //反射得到該類中全部的方法
                    for (Method method : instance.getClass().getMethods()) {
                        //若是是set方法
                        if (method.getName().startsWith("set")
                                && method.getParameterTypes().length == 1
                                && Modifier.isPublic(method.getModifiers())) {
                            Class<?> pt = method.getParameterTypes()[0];
                            try {
                                //得到屬性,好比StubProxyFactoryWrapper類中有Protocol protocol屬性,
                                String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";
                                //得到屬性值,好比Protocol對象,也多是Bean對象
                                Object object = objectFactory.getExtension(pt, property);
                                if (object != null) {
                                    //注入依賴屬性
                                    method.invoke(instance, object);
                                }
                            } catch (Exception e) {
                                logger.error("fail to inject via method " + method.getName() + " of interface " + type.getName() + ": " + e.getMessage(), e);
                            }
                        }
                    }
                }
            } catch (Exception e) {
                logger.error(e.getMessage(), e);
            }
            return instance;
        }

    思路就是是先經過反射得到類中的全部方法,而後找到set方法,找到須要依賴注入的屬性,而後把對象注入進去。

  10. getExtensionClass方法:得到擴展名對應的擴展實現類
    private Class<?> getExtensionClass(String name) {
            if (type == null)
                throw new IllegalArgumentException("Extension type == null");
            if (name == null)
                throw new IllegalArgumentException("Extension name == null");
            Class<?> clazz = getExtensionClasses().get(name);
            if (clazz == null)
                throw new IllegalStateException("No such extension \"" + name + "\" for " + type.getName() + "!");
            return clazz;
        }

    這邊就是調用了getExtensionClasses的方法,該方法解釋在下面給出。

  11. getExtensionClasses方法:得到擴展實現類數組
    private Map<String, Class<?>> getExtensionClasses() {
            Map<String, Class<?>> classes = cachedClasses.get();
            if (classes == null) {
                synchronized (cachedClasses) {
                    classes = cachedClasses.get();
                    if (classes == null) {
                        //從配置文件中,加載擴展實現類數組
                        classes = loadExtensionClasses();
                        cachedClasses.set(classes);
                    }
                }
            }
            return classes;
        }

    這裏思路就是先從緩存中取,若是緩存爲空,則從配置文件中讀取擴展實現類,loadExtensionClasses方法解析在下面給出。

  12. loadExtensionClasses方法:從配置文件中,加載拓展實現類數組

    private Map<String, Class<?>> loadExtensionClasses() {
            final SPI defaultAnnotation = type.getAnnotation(SPI.class);
            if (defaultAnnotation != null) {
                //@SPI內的默認值
                String value = defaultAnnotation.value();
                if ((value = value.trim()).length() > 0) {
                    String[] names = NAME_SEPARATOR.split(value);
                    //只容許有一個默認值
                    if (names.length > 1) {
                        throw new IllegalStateException("more than 1 default extension name on extension " + type.getName() + ": " + Arrays.toString(names));
                    }
                    if (names.length == 1) cachedDefaultName = names[0];
                }
            }
    
            //從配置文件中加載實現類數組
            Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
            loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
            loadDirectory(extensionClasses, DUBBO_DIRECTORY);
            loadDirectory(extensionClasses, SERVICES_DIRECTORY);
            return extensionClasses;
        }

    前一部分邏輯是在把SPI註解中的默認值放到緩存中去,加載實現類數組的邏輯是在後面幾行,關鍵的就是loadDirectory方法(解析在下面給出),而且這裏能夠看出去找配置文件訪問的資源路徑順序。

  13. loadDirectory方法:從一個配置文件中,加載拓展實現類數組
    private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir) {
            //拼接接口全限定名,獲得完整的文件名
            String fileName = dir + type.getName();
            try {
                Enumeration<java.net.URL> urls;
                //獲取ExtensionLoader類信息
                ClassLoader classLoader = findClassLoader();
                if (classLoader != null) {
                    urls = classLoader.getResources(fileName);
                } else {
                    urls = ClassLoader.getSystemResources(fileName);
                }
                if (urls != null) {
                    //遍歷文件
                    while (urls.hasMoreElements()) {
                        java.net.URL resourceURL = urls.nextElement();
                        loadResource(extensionClasses, classLoader, resourceURL);
                    }
                }
            } catch (Throwable t) {
                logger.error("Exception when load extension class(interface: " +
                        type + ", description file: " + fileName + ").", t);
            }
        }

    這邊的思路是先得到完整的文件名,遍歷每個文件,在loadResource方法中去加載每一個文件的內容。

  14. loadResource方法:加載文件中的內容
    private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader, java.net.URL resourceURL) {
            try {
                BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), "utf-8"));
                try {
                    String line;
                    while ((line = reader.readLine()) != null) {
                        //跳過被#註釋的內容
                        final int ci = line.indexOf('#');
                        if (ci >= 0) line = line.substring(0, ci);
                        line = line.trim();
                        if (line.length() > 0) {
                            try {
                                String name = null;
                                int i = line.indexOf('=');
                                if (i > 0) {
                                    //根據"="拆分key跟value
                                    name = line.substring(0, i).trim();
                                    line = line.substring(i + 1).trim();
                                }
                                if (line.length() > 0) {
                                    //加載擴展類
                                    loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name);
                                }
                            } catch (Throwable t) {
                                IllegalStateException e = new IllegalStateException("Failed to load extension class(interface: " + type + ", class line: " + line + ") in " + resourceURL + ", cause: " + t.getMessage(), t);
                                exceptions.put(line, e);
                            }
                        }
                    }
                } finally {
                    reader.close();
                }
            } catch (Throwable t) {
                logger.error("Exception when load extension class(interface: " +
                        type + ", class file: " + resourceURL + ") in " + resourceURL, t);
            }
        }

    該類的主要的邏輯就是讀取裏面的內容,跳過「#」註釋的內容,根據配置文件中的key=value的形式去分割,而後去加載value對應的類。

  15. loadClass方法:根據配置文件中的value加載擴展類
    private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
            //該類是否實現擴展接口
            if (!type.isAssignableFrom(clazz)) {
                throw new IllegalStateException("Error when load extension class(interface: " + type + ", class line: " + clazz.getName() + "), class " + clazz.getName() + "is not subtype of interface.");
            }
            //判斷該類是否爲擴展接口的適配器
            if (clazz.isAnnotationPresent(Adaptive.class)) {
                if (cachedAdaptiveClass == null) {
                    cachedAdaptiveClass = clazz;
                } else if (!cachedAdaptiveClass.equals(clazz)) {
                    throw new IllegalStateException("More than 1 adaptive class found: " + cachedAdaptiveClass.getClass().getName() + ", " + clazz.getClass().getName());
                }
            } else if (isWrapperClass(clazz)) {
                Set<Class<?>> wrappers = cachedWrapperClasses;
                if (wrappers == null) {
                    cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();
                    wrappers = cachedWrapperClasses;
                }
                wrappers.add(clazz);
            } else {
                //經過反射得到構造器對象
                clazz.getConstructor();
                //未配置擴展名,自動生成,例如DemoFilter爲 demo,主要用於兼容java SPI的配置。
                if (name == null || name.length() == 0) {
                    name = findAnnotationName(clazz);
                    if (name.length() == 0) {
                        throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL);
                    }
                }
                // 得到擴展名,能夠是數組,有多個拓擴展名。
                String[] names = NAME_SEPARATOR.split(name);
                if (names != null && names.length > 0) {
                    Activate activate = clazz.getAnnotation(Activate.class);
                    //若是是自動激活的實現類,則加入到緩存
                    if (activate != null) {
                        cachedActivates.put(names[0], activate);
                    }
                    for (String n : names) {
                        if (!cachedNames.containsKey(clazz)) {
                            cachedNames.put(clazz, n);
                        }
                        //緩存擴展實現類
                        Class<?> c = extensionClasses.get(n);
                        if (c == null) {
                            extensionClasses.put(n, clazz);
                        } else if (c != clazz) {
                            throw new IllegalStateException("Duplicate extension " + type.getName() + " name " + n + " on " + c.getName() + " and " + clazz.getName());
                        }
                    }
                }
            }
        }

    重點關注該方法中兼容了jdk的SPI思想。由於jdk的SPI相關的配置文件中是xx.yyy.DemoFilter,並無key,也就是沒有擴展名的概念,全部爲了兼容,經過xx.yyy.DemoFilter生成的擴展名爲demo。

  16. createAdaptiveExtensionClass方法:建立適配器類,相似於dubbo動態生成的Transporter$Adpative這樣的類
    private Class<?> createAdaptiveExtensionClass() {
            //建立動態生成的適配器類代碼
            String code = createAdaptiveExtensionClassCode();
            ClassLoader classLoader = findClassLoader();
            com.alibaba.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
            //編譯代碼,返回該類
            return compiler.compile(code, classLoader);
        }

    這個方法中就作了編譯代碼的邏輯,生成代碼在createAdaptiveExtensionClassCode方法中,createAdaptiveExtensionClassCode方法因爲過長,我不在這邊列出,下面會給出github的網址,讀者可自行查看相關的源碼解析。createAdaptiveExtensionClassCode生成的代碼邏輯能夠對照我上述講的(二)註解@Adaptive中的Transporter$Adpative類來看。

  17. 部分方法比較淺顯易懂,而且沒有影響主功能,全部我不在列舉,該類的其餘方法請在一下網址中查看,這裏強調一點,其中的邏輯不難,難的是屬性的含義要充分去品讀理解,弄清楚各個屬性的含義後,再看一些邏輯就很淺顯易懂了。若是真的看不懂屬性的含義,能夠進入到調用的地方,結合「語境」去理解。
    ExtensionLoader類源碼解析地址: https://github.com/CrazyHZM/i...

(六)AdaptiveExtensionFactory

該類是ExtensionFactory的適配器類,也就是我在(二)註解@Adaptive中提到的第一種適配器類的使用。來看看該類的源碼:

@Adaptive
public class AdaptiveExtensionFactory implements ExtensionFactory {

    //擴展對象的集合,默認的能夠分爲dubbo 的SPI中接口實現類對象或者Spring bean對象
    private final List<ExtensionFactory> factories;

    public AdaptiveExtensionFactory() {
        ExtensionLoader<ExtensionFactory> loader = ExtensionLoader.getExtensionLoader(ExtensionFactory.class);
        List<ExtensionFactory> list = new ArrayList<ExtensionFactory>();
        //遍歷全部支持的擴展名
        for (String name : loader.getSupportedExtensions()) {
            //擴展對象加入到集合中
            list.add(loader.getExtension(name));
        }
        //返回一個不可修改的集合
        factories = Collections.unmodifiableList(list);
    }

    @Override
    public <T> T getExtension(Class<T> type, String name) {
        for (ExtensionFactory factory : factories) {
            //經過擴展接口和擴展名得到擴展對象
            T extension = factory.getExtension(type, name);
            if (extension != null) {
                return extension;
            }
        }
        return null;
    }

}
  1. factories是擴展對象的集合,當用戶沒有本身實現ExtensionFactory接口,則這個屬性就只會有兩種對象,分別是 SpiExtensionFactory 和 SpringExtensionFactory 。
  2. 構造器中是把全部支持的擴展名的擴展對象加入到集合
  3. 實現了接口的getExtension方法,經過接口和擴展名來獲取擴展對象。

(七)SpiExtensionFactory

SPI ExtensionFactory 拓展實現類,看看源碼:

public class SpiExtensionFactory implements ExtensionFactory {

    @Override
    public <T> T getExtension(Class<T> type, String name) {
        //判斷是否爲接口,接口上是否有@SPI註解
        if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {
            //得到擴展加載器
            ExtensionLoader<T> loader = ExtensionLoader.getExtensionLoader(type);
            if (!loader.getSupportedExtensions().isEmpty()) {
                //返回適配器類的對象
                return loader.getAdaptiveExtension();
            }
        }
        return null;
    }

}

(八)ActivateComparator

該類在ExtensionLoader類的getActivateExtension方法中被運用到,做爲自動激活拓展對象的排序器。

public class ActivateComparator implements Comparator<Object> {

    public static final Comparator<Object> COMPARATOR = new ActivateComparator();

    @Override
    public int compare(Object o1, Object o2) {
        //基本排序
        if (o1 == null && o2 == null) {
            return 0;
        }
        if (o1 == null) {
            return -1;
        }
        if (o2 == null) {
            return 1;
        }
        if (o1.equals(o2)) {
            return 0;
        }
        Activate a1 = o1.getClass().getAnnotation(Activate.class);
        Activate a2 = o2.getClass().getAnnotation(Activate.class);
        //使用Activate註解的 `after` 和 `before` 屬性,排序
        if ((a1.before().length > 0 || a1.after().length > 0
                || a2.before().length > 0 || a2.after().length > 0)
                && o1.getClass().getInterfaces().length > 0
                && o1.getClass().getInterfaces()[0].isAnnotationPresent(SPI.class)) {
            ExtensionLoader<?> extensionLoader = ExtensionLoader.getExtensionLoader(o1.getClass().getInterfaces()[0]);
            if (a1.before().length > 0 || a1.after().length > 0) {
                String n2 = extensionLoader.getExtensionName(o2.getClass());
                for (String before : a1.before()) {
                    if (before.equals(n2)) {
                        return -1;
                    }
                }
                for (String after : a1.after()) {
                    if (after.equals(n2)) {
                        return 1;
                    }
                }
            }
            if (a2.before().length > 0 || a2.after().length > 0) {
                String n1 = extensionLoader.getExtensionName(o1.getClass());
                for (String before : a2.before()) {
                    if (before.equals(n1)) {
                        return 1;
                    }
                }
                for (String after : a2.after()) {
                    if (after.equals(n1)) {
                        return -1;
                    }
                }
            }
        }
        // 使用Activate註解的 `order` 屬性,排序。
        int n1 = a1 == null ? 0 : a1.order();
        int n2 = a2 == null ? 0 : a2.order();
        // never return 0 even if n1 equals n2, otherwise, o1 and o2 will override each other in collection like HashSet
        return n1 > n2 ? 1 : -1;
    }

}

關鍵的仍是經過@Activate註解中的值來進行排序。

後記

該部分相關的源碼解析地址: https://github.com/CrazyHZM/i...

該文章講解了dubbo的SPI擴展機制的實現原理,最關鍵的是弄清楚dubbo跟jdk在實現SPI的思想上作了哪些改進和優化,解讀dubbo SPI擴展機制最關鍵的是弄清楚@SPI、@Adaptive、@Activate三個註解的含義,大部分邏輯都被封裝在ExtensionLoader類中。dubbo的不少接口都是擴展接口,解讀該文,也能讓讀者在後續文章中更加容易的去了解dubbo的架構設計。若是我在哪一部分寫的不夠到位或者寫錯了,歡迎給我提意見,個人私人微信號碼:HUA799695226。

相關文章
相關標籤/搜索