成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

dubbo源碼解析(二)Dubbo擴(kuò)展機(jī)制SPI

DirtyMind / 3527人閱讀

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

Dubbo擴(kuò)展機(jī)制SPI

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

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

目標(biāo):讓讀者知道JDK的SPI思想,dubbo的SPI思想,dubbo擴(kuò)展機(jī)制SPI的原理,能夠讀懂實現(xiàn)擴(kuò)展機(jī)制的源碼。

第一篇源碼分析的文章就先來講講dubbo擴(kuò)展機(jī)制spi的原理,瀏覽過dubbo官方文檔的朋友肯定知道,dubbo有大量的spi擴(kuò)展實現(xiàn),包括協(xié)議擴(kuò)展、調(diào)用攔截擴(kuò)展、路由擴(kuò)展等26個擴(kuò)展,并且spi機(jī)制運用到了各個模塊設(shè)計中。所以我打算先講解dubbo的擴(kuò)展機(jī)制spi。

JDK的SPI思想

SPI的全名為Service Provider Interface,面向?qū)ο蟮脑O(shè)計里面,模塊之間推薦基于接口編程,而不是對實現(xiàn)類進(jìn)行硬編碼,這樣做也是為了模塊設(shè)計的可拔插原則。為了在模塊裝配的時候不在程序里指明是哪個實現(xiàn),就需要一種服務(wù)發(fā)現(xiàn)的機(jī)制,jdk的spi就是為某個接口尋找服務(wù)實現(xiàn)。jdk提供了服務(wù)實現(xiàn)查找的工具類:java.util.ServiceLoader,它會去加載META-INF/service/目錄下的配置文件。具體的內(nèi)部實現(xiàn)邏輯為這里先不展開,主要還是講解dubbo關(guān)于spi的實現(xiàn)原理。

Dubbo的SPI擴(kuò)展機(jī)制原理

dubbo自己實現(xiàn)了一套SPI機(jī)制,改進(jìn)了JDK標(biāo)準(zhǔn)的SPI機(jī)制:

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

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

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

我們先來看看SPI擴(kuò)展機(jī)制實現(xiàn)的結(jié)構(gòu)目錄:

(一)注解@SPI

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

value就是該Protocol接口的實現(xiàn)類DubboProtocol,這樣就做到了SPI擴(kuò)展。

(二)注解@Adaptive

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

dubbo提供了兩種方式來實現(xiàn)接口的適配器:

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

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

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

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

我們可以看到在這個接口的bind和connect方法上都有@Adaptive注解,有該注解的方法的參數(shù)必須包含URL,ExtensionLoader會通過createAdaptiveExtensionClassCode方法動態(tài)生成一個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參數(shù)為空則拋出異常。
        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])");
        //這里我在后面會有詳細(xì)介紹
        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:

所有擴(kuò)展點都通過傳遞URL攜帶配置信息,所以適配器中的方法必須攜帶URL參數(shù),才能根據(jù)URL中的配置來選擇對應(yīng)的擴(kuò)展實現(xiàn)。

@Adaptive注解中有一些key值,比如connect方法的注解中有兩個key,分別為“client”和“transporter”,URL會首先去取client對應(yīng)的value來作為我上述(一)注解@SPI中寫到的key值,如果為空,則去取transporter對應(yīng)的value,如果還是為空,則會根據(jù)SPI默認(rèn)的key,也就是netty去調(diào)用擴(kuò)展的實現(xiàn)類,如果@SPI沒有設(shè)定默認(rèn)值,則會拋出IllegalStateException異常。

這樣就比較清楚這個適配器如何去選擇哪個實現(xiàn)類作為本次需要調(diào)用的類,這里最關(guān)鍵的還是強(qiáng)調(diào)了dubbo以URL為總線,運行過程中所有的狀態(tài)數(shù)據(jù)信息都可以通過URL來獲取,比如當(dāng)前系統(tǒng)采用什么序列化,采用什么通信,采用什么負(fù)載均衡等信息,都是通過URL的參數(shù)來呈現(xiàn)的,所以在框架運行過程中,運行到某個階段需要相應(yīng)的數(shù)據(jù),都可以通過對應(yīng)的Key從URL的參數(shù)列表中獲取。

(三)注解@Activate

擴(kuò)展點自動激活加載的注解,就是用條件來控制該擴(kuò)展點實現(xiàn)是否被自動激活加載,在擴(kuò)展實現(xiàn)類上面使用,實現(xiàn)了擴(kuò)展點自動激活的特性,它可以設(shè)置兩個參數(shù),分別是group和value。具體的介紹可以參照官方文檔。

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

先來看看它的源碼:

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

(五)ExtensionLoader

該類是擴(kuò)展加載器,這是dubbo實現(xiàn)SPI擴(kuò)展機(jī)制等核心,幾乎所有實現(xiàn)的邏輯都被封裝在ExtensionLoader中。

詳細(xì)代碼注釋見github:https://github.com/CrazyHZM/i...

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

關(guān)于存放配置文件的路徑變量:

    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尋找擴(kuò)展實現(xiàn)類的配置文件存放路徑,也就是我在上述(一)注解@SPI中講到的以接口全限定名命名的配置文件存放的路徑。區(qū)別在于"META-INF/services/"是dubbo為了兼容jdk的SPI擴(kuò)展機(jī)制思想而設(shè)存在的,"META-INF/dubbo/internal/"是dubbo內(nèi)部提供的擴(kuò)展的配置文件路徑,而"META-INF/dubbo/"是為了給用戶自定義的擴(kuò)展實現(xiàn)配置文件存放。

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

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

擴(kuò)展實現(xiàn)類集合,key為擴(kuò)展實現(xiàn)類,value為擴(kuò)展對象,例如key為Class,value為DubboProtocol對象

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

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

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

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

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

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

getExtensionLoader(Class type):根據(jù)擴(kuò)展點接口來獲得擴(kuò)展加載器。
    public static  ExtensionLoader getExtensionLoader(Class type) {
        //擴(kuò)展點接口為空,拋出異常
        if (type == null)
            throw new IllegalArgumentException("Extension type == null");
        //判斷type是否是一個接口類
        if (!type.isInterface()) {
            throw new IllegalArgumentException("Extension type(" + type + ") is not interface!");
        }
        //判斷是否為可擴(kuò)展的接口
        if (!withExtensionAnnotation(type)) {
            throw new IllegalArgumentException("Extension type(" + type +
                        ") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");
        }

        //從擴(kuò)展加載器集合中取出擴(kuò)展接口對應(yīng)的擴(kuò)展加載器
        ExtensionLoader loader = (ExtensionLoader) EXTENSION_LOADERS.get(type);

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

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

getActivateExtension方法:獲得符合自動激活條件的擴(kuò)展實現(xiàn)類對象集合
    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);
        // 獲得符合自動激活條件的拓展對象數(shù)組
        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"` 。
        //例如, ,代表移除所有默認(rèn)過濾器。
        if (!names.contains(Constants.REMOVE_VALUE_PREFIX + Constants.DEFAULT_KEY)) {

            //獲得擴(kuò)展實現(xiàn)類數(shù)組,把擴(kuò)展實現(xiàn)類放到cachedClasses中
            getExtensionClasses();
            for (Map.Entry entry : cachedActivates.entrySet()) {
                String name = entry.getKey();
                Activate activate = entry.getValue();
                //判斷group值是否存在所有自動激活類中g(shù)roup組中,匹配分組
                if (isMatchGroup(group, activate.group())) {
                    //通過擴(kuò)展名獲得拓展對象
                    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)) {
                //在配置中把自定義的配置放在自動激活的擴(kuò)展對象前面,可以讓自定義的配置先加載
                //例如, ,則 DemoFilter 就會放在默認(rèn)的過濾器前面。
                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重載了四個方法,其實最終的實現(xiàn)都是在最后一個重載方法,因為自動激活類的條件可以分為無條件、只有value以及有g(shù)roup和value三種,具體的可以回顧上述(三)注解@Activate

最后一個getActivateExtension方法有幾個關(guān)鍵點:

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

判斷該配置是否被移除。

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

getExtension方法: 獲得通過擴(kuò)展名獲得擴(kuò)展對象
    @SuppressWarnings("unchecked")
    public T getExtension(String name) {
        if (name == null || name.length() == 0)
            throw new IllegalArgumentException("Extension name == null");
        //查找默認(rèn)的擴(kuò)展實現(xiàn),也就是@SPI中的默認(rèn)值作為key
        if ("true".equals(name)) {
            return getDefaultExtension();
        }
        //緩存中獲取對應(yīng)的擴(kuò)展對象
        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) {
                    //通過擴(kuò)展名創(chuàng)建接口實現(xiàn)類的對象
                    instance = createExtension(name);
                    //把創(chuàng)建的擴(kuò)展對象放入緩存
                    holder.set(instance);
                }
            }
        }
        return (T) instance;
    }

這個方法中涉及到getDefaultExtension方法和createExtension方法,會在后面講到。其他邏輯比較簡單,就是從緩存中取,如果沒有,就創(chuàng)建,然后放入緩存。

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

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

addExtension方法:擴(kuò)展接口的實現(xiàn)類
    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 + ")!");
            }

            //把擴(kuò)展名和擴(kuò)展接口的實現(xiàn)類放入緩存
            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方法:獲得自適應(yīng)擴(kuò)展對象,也就是接口的適配器對象
   @SuppressWarnings("unchecked")
    public T getAdaptiveExtension() {
        Object instance = cachedAdaptiveInstance.get();
        if (instance == null) {
            if (createAdaptiveInstanceError == null) {
                synchronized (cachedAdaptiveInstance) {
                    instance = cachedAdaptiveInstance.get();
                    if (instance == null) {
                        try {
                            //創(chuàng)建適配器對象
                            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;
    }

思路就是先從緩存中取適配器類的對象,如果沒有,則創(chuàng)建一個適配器對象,然后放入緩存,createAdaptiveExtension方法解釋在后面給出。

createExtension方法:通過擴(kuò)展名創(chuàng)建擴(kuò)展接口實現(xiàn)類的對象
    @SuppressWarnings("unchecked")
    private T createExtension(String name) {
        //獲得擴(kuò)展名對應(yīng)的擴(kuò)展實現(xiàn)類
        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);
            }
            //向?qū)ο笾凶⑷胍蕾嚨膶傩裕ㄗ詣友b配)
            injectExtension(instance);
            //創(chuàng)建 Wrapper 擴(kuò)展對象(自動包裝)
            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);
        }
    }

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

injectExtension方法:向創(chuàng)建的拓展注入其依賴的屬性
    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方法,找到需要依賴注入的屬性,然后把對象注入進(jìn)去。

getExtensionClass方法:獲得擴(kuò)展名對應(yīng)的擴(kuò)展實現(xiàn)類
    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;
    }

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

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

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

loadExtensionClasses方法:從配置文件中,加載拓展實現(xiàn)類數(shù)組

   private Map> loadExtensionClasses() {
        final SPI defaultAnnotation = type.getAnnotation(SPI.class);
        if (defaultAnnotation != null) {
            //@SPI內(nèi)的默認(rèn)值
            String value = defaultAnnotation.value();
            if ((value = value.trim()).length() > 0) {
                String[] names = NAME_SEPARATOR.split(value);
                //只允許有一個默認(rèn)值
                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];
            }
        }

        //從配置文件中加載實現(xiàn)類數(shù)組
        Map> extensionClasses = new HashMap>();
        loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
        loadDirectory(extensionClasses, DUBBO_DIRECTORY);
        loadDirectory(extensionClasses, SERVICES_DIRECTORY);
        return extensionClasses;
    }

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

loadDirectory方法:從一個配置文件中,加載拓展實現(xiàn)類數(shù)組
    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方法中去加載每個文件的內(nèi)容。

loadResource方法:加載文件中的內(nèi)容
   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) {
                    //跳過被#注釋的內(nèi)容
                    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) {
                                //根據(jù)"="拆分key跟value
                                name = line.substring(0, i).trim();
                                line = line.substring(i + 1).trim();
                            }
                            if (line.length() > 0) {
                                //加載擴(kuò)展類
                                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);
        }
    }

該類的主要的邏輯就是讀取里面的內(nèi)容,跳過“#”注釋的內(nèi)容,根據(jù)配置文件中的key=value的形式去分割,然后去加載value對應(yīng)的類。

loadClass方法:根據(jù)配置文件中的value加載擴(kuò)展類
   private void loadClass(Map> extensionClasses, java.net.URL resourceURL, Class clazz, String name) throws NoSuchMethodException {
        //該類是否實現(xiàn)擴(kuò)展接口
        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.");
        }
        //判斷該類是否為擴(kuò)展接口的適配器
        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 {
            //通過反射獲得構(gòu)造器對象
            clazz.getConstructor();
            //未配置擴(kuò)展名,自動生成,例如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);
                }
            }
            // 獲得擴(kuò)展名,可以是數(shù)組,有多個拓擴(kuò)展名。
            String[] names = NAME_SEPARATOR.split(name);
            if (names != null && names.length > 0) {
                Activate activate = clazz.getAnnotation(Activate.class);
                //如果是自動激活的實現(xiàn)類,則加入到緩存
                if (activate != null) {
                    cachedActivates.put(names[0], activate);
                }
                for (String n : names) {
                    if (!cachedNames.containsKey(clazz)) {
                        cachedNames.put(clazz, n);
                    }
                    //緩存擴(kuò)展實現(xià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());
                    }
                }
            }
        }
    }

重點關(guān)注該方法中兼容了jdk的SPI思想。因為jdk的SPI相關(guān)的配置文件中是xx.yyy.DemoFilter,并沒有key,也就是沒有擴(kuò)展名的概念,所有為了兼容,通過xx.yyy.DemoFilter生成的擴(kuò)展名為demo。

createAdaptiveExtensionClass方法:創(chuàng)建適配器類,類似于dubbo動態(tài)生成的Transporter$Adpative這樣的類
    private Class createAdaptiveExtensionClass() {
        //創(chuàng)建動態(tài)生成的適配器類代碼
        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的網(wǎng)址,讀者可自行查看相關(guān)的源碼解析。createAdaptiveExtensionClassCode生成的代碼邏輯可以對照我上述講的(二)注解@Adaptive中的Transporter$Adpative類來看。

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

(六)AdaptiveExtensionFactory

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

@Adaptive
public class AdaptiveExtensionFactory implements ExtensionFactory {

    //擴(kuò)展對象的集合,默認(rèn)的可以分為dubbo 的SPI中接口實現(xiàn)類對象或者Spring bean對象
    private final List factories;

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

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

}

factories是擴(kuò)展對象的集合,當(dāng)用戶沒有自己實現(xiàn)ExtensionFactory接口,則這個屬性就只會有兩種對象,分別是 SpiExtensionFactory 和 SpringExtensionFactory 。

構(gòu)造器中是把所有支持的擴(kuò)展名的擴(kuò)展對象加入到集合

實現(xiàn)了接口的getExtension方法,通過接口和擴(kuò)展名來獲取擴(kuò)展對象。

(七)SpiExtensionFactory

SPI ExtensionFactory 拓展實現(xiàn)類,看看源碼:

public class SpiExtensionFactory implements ExtensionFactory {

    @Override
    public  T getExtension(Class type, String name) {
        //判斷是否為接口,接口上是否有@SPI注解
        if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {
            //獲得擴(kuò)展加載器
            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;
    }

}

關(guān)鍵的還是通過@Activate注解中的值來進(jìn)行排序。

后記
該部分相關(guān)的源碼解析地址:https://github.com/CrazyHZM/i...

該文章講解了dubbo的SPI擴(kuò)展機(jī)制的實現(xiàn)原理,最關(guān)鍵的是弄清楚dubbo跟jdk在實現(xiàn)SPI的思想上做了哪些改進(jìn)和優(yōu)化,解讀dubbo SPI擴(kuò)展機(jī)制最關(guān)鍵的是弄清楚@SPI、@Adaptive、@Activate三個注解的含義,大部分邏輯都被封裝在ExtensionLoader類中。dubbo的很多接口都是擴(kuò)展接口,解讀該文,也能讓讀者在后續(xù)文章中更加容易的去了解dubbo的架構(gòu)設(shè)計。如果我在哪一部分寫的不夠到位或者寫錯了,歡迎給我提意見,我的私人微信號碼:HUA799695226。

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/71926.html

相關(guān)文章

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

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

    Faremax 評論0 收藏0
  • 聊聊Dubbo - Dubbo擴(kuò)展機(jī)制源碼解析

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

    lmxdawn 評論0 收藏0
  • dubboSPI

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

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

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

    AlphaWallet 評論0 收藏0
  • dubbo擴(kuò)展機(jī)制

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

    Rindia 評論0 收藏0

發(fā)表評論

0條評論

DirtyMind

|高級講師

TA的文章

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