1. 程式人生 > 程式設計 >Dubbo原始碼解析(二)Dubbo擴充套件機制SPI

Dubbo原始碼解析(二)Dubbo擴充套件機制SPI

Dubbo擴充套件機制SPI

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

我會在之後的每篇文章前都寫一個目標,為了讓讀者一眼就能知道本文是否是你需要尋找的資料。


目標:讓讀者知道JDK的SPI思想,dubbo的SPI思想,dubbo擴充套件機制SPI的原理,能夠讀懂實現擴充套件機制的原始碼。

第一篇原始碼分析的文章就先來講講dubbo擴充套件機制spi的原理,瀏覽過dubbo官方檔案的朋友肯定知道,dubbo有大量的spi擴充套件實現,包括協議擴充套件、呼叫攔截擴充套件、路由擴充套件等26個擴充套件,並且spi機制運用到了各個模組設計中。所以我打算先講解dubbo的擴充套件機制spi。

JDK的SPI思想

SPI的全名為Service Provider Interface,面向物件的設計裡面,模組之間推薦基於介面程式設計,而不是對實現類進行硬編碼,這樣做也是為了模組設計的可拔插原則。為了在模組裝配的時候不在程式裡指明是哪個實現,就需要一種服務發現的機制,jdk的spi就是為某個介面尋找服務實現。jdk提供了服務實現查詢的工具類:java.util.ServiceLoader,它會去載入META-INF/service/目錄下的配置檔案。具體的內部實現邏輯為這裡先不展開,主要還是講解dubbo關於spi的實現原理。

Dubbo的SPI擴充套件機制原理

dubbo自己實現了一套SPI機制,改進了JDK標準的SPI機制:

  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-INF\dubbo\internal\com.alibaba.dubbo.rpc.Protocol檔案中找該key對應的value,看下圖:

protocol的配置檔案

value就是該Protocol介面的實現類DubboProtocol,這樣就做到了SPI擴充套件。

(二)註解@Adaptive

該註解為了保證dubbo在內部呼叫具體實現的時候不是硬編碼來指定引用哪個實現,也就是為了適配一個介面的多種實現,這樣做符合模組介面設計的可插拔原則,也增加了整個框架的靈活性,該註解也實現了擴充套件點自動裝配的特性

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","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

擴充套件點自動啟用載入的註解,就是用條件來控制該擴充套件點實現是否被自動啟用載入,在擴充套件實現類上面使用,實現了擴充套件點自動啟用的特性,它可以設定兩個引數,分別是group和value。具體的介紹可以參照官方檔案。

擴充套件點自動啟用地址:dubbo.apache.org/zh-cn/docs/…

(四)介面ExtensionFactory

先來看看它的原始碼:

ExtensionFactory原始碼

該介面是擴充套件工廠介面類,它本身也是一個擴充套件介面,有SPI的註解。該工廠介面提供的就是獲取實現類的例項,它也有兩種擴充套件實現,分別是SpiExtensionFactory和SpringExtensionFactory代表著兩種不同方式去獲取例項。而具體選擇哪種方式去獲取實現類的例項,則在介面卡AdaptiveExtensionFactory中制定了規則。具體規則看下面的原始碼解析。

(五)ExtensionLoader

該類是擴充套件載入器,這是dubbo實現SPI擴充套件機制等核心,幾乎所有實現的邏輯都被封裝在ExtensionLoader中。

詳細程式碼註釋見github:github.com/CrazyHZM/in…

  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,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 返回擴充套件點時,包裝在真正的擴充套件點實現外,這實現了擴充套件點自動包裝的特性。通俗點說,就是一個介面有很多的實現類,這些實現類會有一些公共的邏輯,如果在每個實現類寫一遍這個公共邏輯,那麼程式碼就會重複,所以增加了這個Wrapper類來包裝,把公共邏輯寫到Wrapper類中,有點類似AOP切面程式設計思想。這部分解釋也可以結合官方檔案:

      擴充套件點自動包裝的特性地址:dubbo.apache.org/zh-cn/docs/…

  2. getExtensionLoader(Class 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方法:獲得符合自動啟用條件的擴充套件實現類物件集合
    	/**
         * 獲得符合自動啟用條件的擴充套件實現類物件集合(適用沒有group條件的自動啟用類)
         * @param url
         * @param key
         * @return
         */
        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);
        }
    	/**
         * 獲得符合自動啟用條件的擴充套件實現類物件集合(適用含有value和group條件的自動啟用類)
         * @param url
         * @param key
         * @param group
         * @return
         */
        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方法: 獲得通過副檔名獲得擴充套件物件
        /**
         * 通過副檔名獲得擴充套件物件
         * @param name
         * @return
         */
        @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,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,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類原始碼解析地址:github.com/CrazyHZM/in…

(六)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註解中的值來進行排序。

後記

該部分相關的原始碼解析地址:github.com/CrazyHZM/in…

該文章講解了dubbo的SPI擴充套件機制的實現原理,最關鍵的是弄清楚dubbo跟jdk在實現SPI的思想上做了哪些改進和優化,解讀dubbo SPI擴充套件機制最關鍵的是弄清楚@SPI、@Adaptive、@Activate三個註解的含義,大部分邏輯都被封裝在ExtensionLoader類中。dubbo的很多介面都是擴充套件介面,解讀該文,也能讓讀者在後續文章中更加容易的去了解dubbo的架構設計。如果我在哪一部分寫的不夠到位或者寫錯了,歡迎給我提意見。