国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

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

DirtyMind / 3506人閱讀

摘要:二注解該注解為了保證在內部調用具體實現的時候不是硬編碼來指定引用哪個實現,也就是為了適配一個接口的多種實現,這樣做符合模塊接口設計的可插拔原則,也增加了整個框架的靈活性,該注解也實現了擴展點自動裝配的特性。

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機制:

JDK標準的SPI只能通過遍歷來查找擴展點和實例化,有可能導致一次性加載所有的擴展點,如果不是所有的擴展點都被用到,就會導致資源的浪費。dubbo每個擴展點都有多種實現,例如com.alibaba.dubbo.rpc.Protocol接口有InjvmProtocol、DubboProtocol、RmiProtocol、HttpProtocol、HessianProtocol等實現,如果只是用到其中一個實現,可是加載了全部的實現,會導致資源的浪費。

把配置文件中擴展實現的格式修改,例如META-INF/dubbo/com.xxx.Protocol里的com.foo.XxxProtocol格式改為了xxx = com.foo.XxxProtocol這種以鍵值對的形式,這樣做的目的是為了讓我們更容易的定位到問題,比如由于第三方庫不存在,無法初始化,導致無法加載擴展名(“A”),當用戶配置使用A時,dubbo就會報無法加載擴展名的錯誤,而不是報哪些擴展名的實現加載失敗以及錯誤原因,這是因為原來的配置格式沒有把擴展名的id記錄,導致dubbo無法拋出較為精準的異常,這會加大排查問題的難度。所以改成key-value的形式來進行配置。

dubbo的SPI機制增加了對IOC、AOP的支持,一個擴展點可以直接通過setter注入到其他擴展點。

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

(一)注解@SPI

在某個接口上加上@SPI注解后,表明該接口為可擴展接口。我用協議擴展接口Protocol來舉例子,如果使用者在都沒有指定protocol屬性的話,那么就會默認DubboProtocol就是接口Protocol,因為在Protocol上有@SPI("dubbo")注解。而這個protocol屬性值或者默認值會被當作該接口的實現類中的一個key,dubbo會去META-INFdubbointernalcom.alibaba.dubbo.rpc.Protocol文件中找該key對應的value,看下圖:

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

(二)注解@Adaptive

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

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

在實現類上面加上@Adaptive注解,表明該實現類是該接口的適配器。

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

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

我們從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:

所有擴展點都通過傳遞URL攜帶配置信息,所以適配器中的方法必須攜帶URL參數,才能根據URL中的配置來選擇對應的擴展實現。

@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。具體的介紹可以參照官方文檔。

擴展點自動激活地址:http://dubbo.apache.org/zh-cn...
(四)接口ExtensionFactory

先來看看它的源碼:

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

(五)ExtensionLoader

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

詳細代碼注釋見github:https://github.com/CrazyHZM/i...

屬性(選取關鍵屬性進行展開講解,其余見github注釋)

關于存放配置文件的路徑變量:

    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/"是為了給用戶自定義的擴展實現配置文件存放。

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

    private static final ConcurrentMap, ExtensionLoader> EXTENSION_LOADERS = new ConcurrentHashMap, ExtensionLoader>();

擴展實現類集合,key為擴展實現類,value為擴展對象,例如key為Class,value為DubboProtocol對象

    private static final ConcurrentMap, Object> EXTENSION_INSTANCES = new ConcurrentHashMap, Object>();

以下屬性都是cache開頭的,都是出于性能和資源的優化,才做的緩存,讀取擴展配置后,會先進行緩存,等到真正需要用到某個實現時,再對該實現類的對象進行初始化,然后對該對象也進行緩存。

    //以下提到的擴展名就是在配置文件中的key值,類似于“dubbo”等

    //緩存的擴展名與拓展類映射,和cachedClasses的key和value對換。
    private final ConcurrentMap, String> cachedNames = new ConcurrentHashMap, String>();
    //緩存的擴展實現類集合
    private final Holder>> cachedClasses = new Holder>>();
    //擴展名與加有@Activate的自動激活類的映射
    private final Map cachedActivates = new ConcurrentHashMap();
    //緩存的擴展對象集合,key為擴展名,value為擴展對象
    //例如Protocol擴展,key為dubbo,value為DubboProcotol
    private final ConcurrentMap> cachedInstances = new ConcurrentHashMap cachedAdaptiveInstance = new Holder();
    //緩存的自適應擴展對象的類,例如AdaptiveExtensionFactory類
    private volatile Class cachedAdaptiveClass = null;
    //緩存的默認擴展名,就是@SPI中設置的值
    private String cachedDefaultName;
    //創建cachedAdaptiveInstance異常
    private volatile Throwable createAdaptiveInstanceError;
    //拓展Wrapper實現類集合
    private Set> cachedWrapperClasses;
    //拓展名與加載對應拓展類發生的異常的映射
    private Map exceptions = new ConcurrentHashMap();

這里提到了Wrapper類的概念。那我就解釋一下:Wrapper類也實現了擴展接口,但是Wrapper類的用途是ExtensionLoader 返回擴展點時,包裝在真正的擴展點實現外,這實現了擴展點自動包裝的特性。通俗點說,就是一個接口有很多的實現類,這些實現類會有一些公共的邏輯,如果在每個實現類寫一遍這個公共邏輯,那么代碼就會重復,所以增加了這個Wrapper類來包裝,把公共邏輯寫到Wrapper類中,有點類似AOP切面編程思想。這部分解釋也可以結合官方文檔:

擴展點自動包裝的特性地址:http://dubbo.apache.org/zh-cn...

getExtensionLoader(Class type):根據擴展點接口來獲得擴展加載器。
    public static  ExtensionLoader getExtensionLoader(Class 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 loader = (ExtensionLoader) EXTENSION_LOADERS.get(type);

        //如果為空,則創建該擴展接口的擴展加載器,并且添加到EXTENSION_LOADERS
        if (loader == null) {
           EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader(type));
                loader = (ExtensionLoader) EXTENSION_LOADERS.get(type);
        }
        return loader;
    }

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

getActivateExtension方法:獲得符合自動激活條件的擴展實現類對象集合
    public List getActivateExtension(URL url, String key) {
        return getActivateExtension(url, key, null);
    }
    //棄用
    public List getActivateExtension(URL url, String[] values) {
        return getActivateExtension(url, values, null);
    }

    public List 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 getActivateExtension(URL url, String[] values, String group) {
        List exts = new ArrayList();
        List names = values == null ? new ArrayList(0) : Arrays.asList(values);

        //判斷不存在配置 `"-name"` 。
        //例如, ,代表移除所有默認過濾器。
        if (!names.contains(Constants.REMOVE_VALUE_PREFIX + Constants.DEFAULT_KEY)) {

            //獲得擴展實現類數組,把擴展實現類放到cachedClasses中
            getExtensionClasses();
            for (Map.Entry entry : cachedActivates.entrySet()) {
                String name = entry.getKey();
                Activate activate = entry.getValue();
                //判斷group值是否存在所有自動激活類中group組中,匹配分組
                if (isMatchGroup(group, activate.group())) {
                    //通過擴展名獲得拓展對象
                    T ext = getExtension(name);
                    //不包含在自定義配置里。如果包含,會在下面的代碼處理。
                    //判斷是否配置移除。例如 ,則 MonitorFilter 會被移除
                    //判斷是否激活
                    if (!names.contains(name)
                            && !names.contains(Constants.REMOVE_VALUE_PREFIX + name)
                            && isActive(activate, url)) {
                        exts.add(ext);
                    }
                }
            }
            //排序
            Collections.sort(exts, ActivateComparator.COMPARATOR);
        }
        List usrs = new ArrayList();
        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)) {
                //在配置中把自定義的配置放在自動激活的擴展對象前面,可以讓自定義的配置先加載
                //例如, ,則 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方法有幾個關鍵點:

group的值合法判斷,因為group可選"provider"或"consumer"。

判斷該配置是否被移除。

如果有自定義配置,并且需要放在自動激活擴展實現對象加載前,那么需要先存放自定義配置。

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 holder = cachedInstances.get(name);
        if (holder == null) {
            cachedInstances.putIfAbsent(name, new Holder());
            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方法,會在后面講到。其他邏輯比較簡單,就是從緩存中取,如果沒有,就創建,然后放入緩存。

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

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

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;
        }
    }

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方法解釋在后面給出。

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> 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方法解析在下面給出。

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方法,找到需要依賴注入的屬性,然后把對象注入進去。

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的方法,該方法解釋在下面給出。

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

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

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

   private Map> 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> extensionClasses = new HashMap>();
        loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
        loadDirectory(extensionClasses, DUBBO_DIRECTORY);
        loadDirectory(extensionClasses, SERVICES_DIRECTORY);
        return extensionClasses;
    }

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

loadDirectory方法:從一個配置文件中,加載拓展實現類數組
    private void loadDirectory(Map> extensionClasses, String dir) {
        //拼接接口全限定名,得到完整的文件名
        String fileName = dir + type.getName();
        try {
            Enumeration 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方法中去加載每個文件的內容。

loadResource方法:加載文件中的內容
   private void loadResource(Map> 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對應的類。

loadClass方法:根據配置文件中的value加載擴展類
   private void loadClass(Map> 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> wrappers = cachedWrapperClasses;
            if (wrappers == null) {
                cachedWrapperClasses = new ConcurrentHashSet>();
                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。

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類來看。

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

(六)AdaptiveExtensionFactory

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

@Adaptive
public class AdaptiveExtensionFactory implements ExtensionFactory {

    //擴展對象的集合,默認的可以分為dubbo 的SPI中接口實現類對象或者Spring bean對象
    private final List factories;

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

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

}

factories是擴展對象的集合,當用戶沒有自己實現ExtensionFactory接口,則這個屬性就只會有兩種對象,分別是 SpiExtensionFactory 和 SpringExtensionFactory 。

構造器中是把所有支持的擴展名的擴展對象加入到集合

實現了接口的getExtension方法,通過接口和擴展名來獲取擴展對象。

(七)SpiExtensionFactory

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

public class SpiExtensionFactory implements ExtensionFactory {

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

}
(八)ActivateComparator

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

public class ActivateComparator implements Comparator {

    public static final Comparator 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。

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/71926.html

相關文章

  • dubbo源碼解析(八)遠程通信——開篇

    摘要:而編碼器是講應用程序的數據轉化為網絡格式,解碼器則是講網絡格式轉化為應用程序,同時具備這兩種功能的單一組件就叫編解碼器。在中是老的編解碼器接口,而是新的編解碼器接口,并且已經用把適配成了。 遠程通訊——開篇 目標:介紹之后解讀遠程通訊模塊的內容如何編排、介紹dubbo-remoting-api中的包結構設計以及最外層的的源碼解析。 前言 服務治理框架中可以大致分為服務通信和服務管理兩個...

    Faremax 評論0 收藏0
  • 聊聊Dubbo - Dubbo擴展機制源碼解析

    摘要:什么是類那什么樣類的才是擴展機制中的類呢類是一個有復制構造函數的類,也是典型的裝飾者模式。代碼如下有一個參數是的復制構造函數有一個構造函數,參數是擴展點,所以它是一個擴展機制中的類。 摘要:?在Dubbo可擴展機制實戰中,我們了解了Dubbo擴展機制的一些概念,初探了Dubbo中LoadBalance的實現,并自己實現了一個LoadBalance。是不是覺得Dubbo的擴展機制很不錯呀...

    lmxdawn 評論0 收藏0
  • dubboSPI

    摘要:簡介全稱為,是一種服務發現機制。的本質是將接口實現類的全限定名配置在文件中,并由服務加載器讀取配置文件,加載實現類。不過,并未使用原生的機制,而是對其進行了增強,使其能夠更好的滿足需求。并未使用,而是重新實現了一套功能更強的機制。 1、SPI簡介 SPI 全稱為 Service Provider Interface,是一種服務發現機制。SPI 的本質是將接口實現類的全限定名配置在文件中...

    UnixAgain 評論0 收藏0
  • dubbo個人理解于應用章(

    摘要:當提供程序線程池耗盡時,不能發送到使用者端。一些錯誤修正動態配置不能刪除,支持參數,監控統計問題等新功能支持手冊線程池耗盡時自動堆棧轉儲。在注冊表無法連接時被阻止。正常關機,在注冊表取消注冊和線程池關閉之間增加額外的等待時間。 dubbo分析showImg(https://segmentfault.com/img/bVbam2f?w=1726&h=686); dubbo為什么要對接sp...

    AlphaWallet 評論0 收藏0
  • dubbo擴展機制

    摘要:在中配置,以配置為例整個,最先使用的地方從里面讀取這個配置使用接口的中獲取具體的實現類中有兩個值當主線程被外部終止時,會觸發,執行的與方法通知下面的鎖操作,主線程正常走完代碼,并最終停止。 spring是如何啟動容器的 常見的一種在本地使用main方法啟動spring的方法 public static void main(String[] args) throws Except...

    Rindia 評論0 收藏0

發表評論

0條評論

DirtyMind

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<