Dubbo中SPI源碼解析

從兩個示例代碼桃犬,介紹dubbo的SPI的使用以及相關(guān)源碼分析恨豁,分析了獲取擴展實現(xiàn)和獲取自適應(yīng)擴展點實現(xiàn)的源碼沽瞭,最后簡單說了下ExtensionFactory的流程,看完就可以理解為什么dubbo是自包含的了豆同。從上往下看番刊,再回頭看,應(yīng)該能看明白影锈,文章比較長芹务,希望能耐心讀下去蝉绷。如果有錯誤的地方希望能指出來,我也理解不是太完整或者表述不是太明白锄禽。

ExtensionLoader使用以及簡單流程分析

假設(shè)有這樣一段示例代碼:

public static void main(String[] args) {
        ExtensionLoader<Protocol> extensionLoader = ExtensionLoader.getExtensionLoader(Protocol.class);

        Protocol dubboProtocol = extensionLoader.getExtension("dubbo");
        System.out.println(dubboProtocol.getDefaultPort());
    }

我們先通過ExtensionLoader.getExtensionLoader(Protocol.class)獲取ExtensionLoader實例潜必,然后通過getExtension("dubbo")獲取到具體的Protocol實現(xiàn)DubboProtocol

首先看下獲取ExtensionLoader實例的過程:

  1. 各種校驗沃但。
  2. 從緩存中獲取指定類型的ExtensionLoader實例磁滚。
  3. 如果緩存中不存在的話,就新建一個ExtensionLoader實例宵晚,并放入緩存垂攘。
  4. 返回ExtensionLoader實例。

這部分源碼如下:

public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
    // 擴展點類型不能為空
    if (type == null)
        throw new IllegalArgumentException("Extension type == null");
    // 擴展點類型只能是接口類型的
    if(!type.isInterface()) {
        throw new IllegalArgumentException("Extension type(" + type + ") is not interface!");
    }
    // 沒有添加@SPI注解
    if(!withExtensionAnnotation(type)) {
        throw new IllegalArgumentException("Extension type(" + type + 
                ") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");
    }
    // 先從緩存中獲取指定類型的ExtensionLoader
    ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
    // 緩存中不存在
    if (loader == null) {
        /**
         * 創(chuàng)建一個新的ExtensionLoader實例淤刃,放到緩存中去
         * 對于每一個擴展晒他,dubbo中只有個對應(yīng)的ExtensionLoader實例
         */
        EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
        loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
    }
    return loader;
}

ExtensionLoader緩存

前面的校驗可以參考注釋,這里先說下緩存EXTENSION_LOADERS

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

可以看到每個SPI擴展的ExtensionLoader的實例只有一個逸贾,緩存的key就是具體SPI接口類型陨仅,比如com.alibaba.dubbo.rpc.Protocol作為key。

ExtensionLoader實例化

new ExtensionLoader<T>(type)這里做了什么铝侵?

private ExtensionLoader(Class<?> type) {
    this.type = type;
    objectFactory = (
            type == ExtensionFactory.class ?
                    null :
                    ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension()
    );
}

上面示例代碼執(zhí)行后灼伤,第一次到這里,type是com.alibaba.dubbo.rpc.Protocol咪鲜,所以這里會先執(zhí)行ExtensionLoader.getExtensionLoader(ExtensionFactory.class)狐赡,然后執(zhí)行getAdaptiveExtension()

也就是說如果是第一次執(zhí)行獲取Protocol類型的ExtensionLoader的實例的話疟丙,會先獲取ExtensionFactory類型的ExtensionLoader實例颖侄。為什么要先獲取ExtensionFactory類型的ExtensionLoader的實例呢?因為ExtensionFactory是用來生成擴展點具體實現(xiàn)的工廠享郊,這里暫時先到這里览祖,后面會再說ExtensionFactory相關(guān)的東西。

獲取完了ExtensionFactory類型的ExtensionLoader后炊琉,緊接著調(diào)用getAdaptiveExtension()方法來獲取一個自適應(yīng)的ExtensionFactory實例穴墅,獲取自適應(yīng)AdaptiveExtensionFactory實例的原因是ExtensionFactory會有多個實現(xiàn),這樣可以在運行時來決定調(diào)用哪個具體實現(xiàn)温自,而不是直接寫死使用哪個具體實現(xiàn)。

ExtensionFactory的具體實現(xiàn)有三個:

  • AdaptiveExtensionFactory
  • SpiExtensionFactory
  • SpringExtensionFactory

其中AdaptiveExtensionFactory注解了@Adaptive注解皇钞,是ExtensionFactory這個SPI接口的自適應(yīng)實現(xiàn)悼泌,如果在運行時需要獲取一個ExtensionFactory的實現(xiàn)時,會調(diào)用AdaptiveExtensionFactory來進行動態(tài)獲取夹界。

說明一下馆里,一個擴展點最多只能有一個自適應(yīng)實現(xiàn),也就是一個擴展點的具體實現(xiàn)類最多只能有一個可以在類級別上注解@Adaptive。如果一個擴展點沒有任何一個實現(xiàn)在類級別上注解@Adaptive鸠踪,那么dubbo會在運行時動態(tài)生成一個自適應(yīng)實現(xiàn)類丙者,比如Protocol的具體實現(xiàn)類就沒有任何一個有在類級別上注解了@Adaptive,dubbo會自動生成一個名字是Protocol$Adpative的自適應(yīng)實現(xiàn)類营密。

使用ExtensionLoader獲取擴展點實現(xiàn)

上面的步驟完成了獲取Protocol類型的ExtensionLoader的實例械媒,同時也完成了ExtensionFactory類型的ExtensionLoader實例的加載,同時也生成了ExtensionFactory的自適應(yīng)實現(xiàn)评汰,接下來繼續(xù)往下走:

Protocol dubboProtocol = extensionLoader.getExtension("dubbo");

獲取了Protocol類型的ExtensionLoader實例后纷捞,就可以根據(jù)名字來加載具體的實現(xiàn)類了,Protocol的具體實現(xiàn)類有:

  • DubboProtocol
  • HessianProtocol
  • HttpProtocol
  • ThriftProtocol
  • InjvmProtocol
  • RmiProtocol
  • WebServiceProtocol
  • RegistryProtocol
  • RedisProtocol
  • MemcachedProtocol
  • 一些Wrapper類

可以看到Protocol有很多具體的實現(xiàn)被去,根據(jù)使用協(xié)議的不同主儡,可以動態(tài)選擇具體使用哪一個Protocol實現(xiàn)。

繼續(xù)看getExtension()方法:

public T getExtension(String name) {
    if (name == null || name.length() == 0)
        throw new IllegalArgumentException("Extension name == null");
    // 獲取默認(rèn)實現(xiàn)
    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) {
                // 緩存不存在惨缆,創(chuàng)建實例
                instance = createExtension(name);
                // 加入緩存
                holder.set(instance);
            }
        }
    }
    return (T) instance;
}

該方法是根據(jù)指定的名字來獲取具體的擴展點的實現(xiàn)的實例糜值,比如我們這里傳的name是dubbo,就會獲取DubboProtocol的實例坯墨,具體步驟如下:

  1. 校驗
  2. 如果name是true寂汇,就獲取默認(rèn)擴展點的實現(xiàn)實例
  3. 從緩存中獲取擴展點實現(xiàn)實例
  4. 如果緩存中不存在,就根據(jù)name創(chuàng)建具體的擴展點實現(xiàn)實例
  5. 返回name對應(yīng)的具體擴展點實現(xiàn)的實例

擴展點實現(xiàn)的實例緩存

獲取默認(rèn)擴展點實現(xiàn)實例暫時不說畅蹂,先看下cachedInstances緩存:

private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<String, Holder<Object>>();

這里緩存了擴展點具體實現(xiàn)的實例健无,key是擴展點的名字,比如DubboProtocol的實例液斜,key就是dubbo累贤,value是DubboProtocol的實例,Holder中持有DubboProtocol的實例少漆。

創(chuàng)建擴展點實現(xiàn)實例

接下來看根據(jù)name創(chuàng)建具體擴展點實現(xiàn)實例的方法createExtension(name)方法臼膏,該方法的代碼如下:

private T createExtension(String name) {
    /**
     * getExtensionClasses加載當(dāng)前擴展點的所有實現(xiàn)
     * 比如:
     * 我們在使用ExtensionLoader.getExtensionLoader(Protocol.class)
     * 獲取Protocol的ExtensionLoader的時候,就已經(jīng)設(shè)置了當(dāng)前ExtensionLoader
     * 的類型是Protocol的示损,所以這里獲取的時候就是Protocol的所有實現(xiàn)渗磅。
     *
     * 獲取到所有的實現(xiàn)之后,getExtensionClasses()返回的是Map<String, Class<?>>
     */
    Class<?> clazz = getExtensionClasses().get(name);
    if (clazz == null) {
        throw findException(name);
    }
    try {
        /**
         * 從緩存中獲取已經(jīng)創(chuàng)建的擴展點的實現(xiàn)的實例
         * 如果還沒有检访,就根據(jù)Class通過反射來創(chuàng)建具體的實例始鱼,
         * 并放到緩存中去
         */
        T instance = (T) EXTENSION_INSTANCES.get(clazz);
        if (instance == null) {
            EXTENSION_INSTANCES.putIfAbsent(clazz, (T) clazz.newInstance());
            instance = (T) EXTENSION_INSTANCES.get(clazz);
        }
        /**
         * 向?qū)嵗凶⑷胍蕾嚨臄U展
         * 如果一個擴展點A依賴了其他的擴展點B,并且有setter方法
         * 就會執(zhí)行將擴展點B注入擴展點A的操作
         */
        injectExtension(instance);
        /**
         * 如果擴展點有包裝類脆贵,將擴展點進行包裝
         * 包裝后如果也依賴了其他擴展點医清,也需要注入其他擴展點
         */
        Set<Class<?>> wrapperClasses = cachedWrapperClasses;
        if (wrapperClasses != null && wrapperClasses.size() > 0) {
            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);
    }
}

該方法根據(jù)擴展點的名字來創(chuàng)建具體擴展點實現(xiàn)的實例,具體步驟如下:

  1. 通過getExtensionClasses()方法將當(dāng)前擴展點的所有的實現(xiàn)類進行加載卖氨,如果是@Adaptive注解的自適應(yīng)實現(xiàn)類会烙,則放到cachedAdaptiveClass緩存中负懦;如果是包裝類,則放到cachedWrapperCalsses緩存中柏腻。經(jīng)過這一步纸厉,擴展點的所有實現(xiàn)都已經(jīng)解析加載。
  2. 根據(jù)名字獲取到具體的某一個擴展點實現(xiàn)類五嫂,并去EXTENSION_INSTANCES緩存中查詢是不是有實例颗品,如果沒有的話,就使用反射創(chuàng)建一個實例贫导。
  3. 如果該實例中依賴了其他的擴展點(需要有setter方法)抛猫,需要將依賴的擴展點進行注入。
  4. 如果擴展點有包裝類孩灯,則將擴展點進行包裝娩鹉,如果包裝后扛伍,也依賴了其他的擴展點(需要有setter方法)羡儿,需要將依賴的擴展點進行注入撮胧。
  5. 返回注入和包裝后的擴展點實現(xiàn)的實例,在我們的這個例子中返回的不是DubboProtocol實例了讥巡,而是經(jīng)過了ProtocolFilterWrapper和ProtocolListenerWrapper包裝后的實例掀亩。

總體的流程就算說完了,已經(jīng)獲取到了名字為dubbo的Protocol的實現(xiàn)的實例欢顷,接下來的執(zhí)行最后一行代碼槽棍,得到結(jié)果:

System.out.println(dubboProtocol.getDefaultPort());

加載擴展點實現(xiàn)類的Class

接下來我們看看getExtensionClasses()方法具體做了什么,該方法是用來加載當(dāng)前擴展點的所有實現(xiàn)的class的抬驴,具體代碼如下:

private Map<String, Class<?>> getExtensionClasses() {
    /**
     * 先從緩存中獲取炼七,不存在的話就調(diào)用loadExtensionClasses進行加載
     * cachedClasses緩存中存儲了當(dāng)前擴展點所有的實現(xiàn)類
     */
    Map<String, Class<?>> classes = cachedClasses.get();
    if (classes == null) {
        synchronized (cachedClasses) {
            classes = cachedClasses.get();
            if (classes == null) {
                /**
                 * 如果沒有加載Extension的實現(xiàn),進行掃描加載布持,完成后緩存起來
                 * 每個擴展點豌拙,其實現(xiàn)的加載只會執(zhí)行一次
                 * 例如,如果Protocol的某個具體實現(xiàn)加載出錯了题暖,沒有放到緩存中去
                 * 后面再使用按傅,也不會再進行加載了。
                 */
                classes = loadExtensionClasses();
                // 緩存起來
                cachedClasses.set(classes);
            }
        }
    }
    return classes;
}

這里也只是嘗試從緩存中獲取胧卤,如果緩存中不存在的話唯绍,就進行具體的加載邏輯。但是這里有個點要注意枝誊,一個擴展點的的實現(xiàn)類加載只會執(zhí)行一次推捐。

繼續(xù)往下走就是真正的加載擴展點的實現(xiàn)邏輯了,代碼如下:

private Map<String, Class<?>> loadExtensionClasses() {
    final SPI defaultAnnotation = type.getAnnotation(SPI.class);
    if(defaultAnnotation != null) {
        // 當(dāng)前擴展點的默認(rèn)實現(xiàn)名字侧啼,如果有的話進行緩存
        String value = defaultAnnotation.value();
        if(value != null && (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];
        }
    }

    // 從配置文件中加載擴展實現(xiàn)類
    Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
    // 從META-INF/dubbo/internal目錄下加載
    loadFile(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
    // 從META-INF/dubbo/目錄下加載
    loadFile(extensionClasses, DUBBO_DIRECTORY);
    // 從META-INF/services/下加載
    loadFile(extensionClasses, SERVICES_DIRECTORY);
    return extensionClasses;
}

這里面邏輯也挺簡單的牛柒,先獲取擴展點的默認(rèn)名字,如果有的話進行緩存痊乾;然后就從配置文件中加載具體的實現(xiàn)類了皮壁,加載的位置有三個,請參照代碼里的注釋哪审。

具體的從配置文件中加載的代碼蛾魄,就不在貼出來了,太長了湿滓。說下大概的邏輯:

  1. 組裝配置文件名字滴须,加載配置文件,遍歷文件中每一行進行處理叽奥。
  2. 加載配置文件中配置的實現(xiàn)類扔水。
  3. 如果是注解了@Adaptive注解的實現(xiàn)類,加入到cachedAdaptiveClass緩存中朝氓。
  4. 如果是包裝類型的實現(xiàn)類魔市,加入到cachedWrapperClasses緩存中。
  5. 如果是除了上面兩種的類赵哲,放到extensionClasses這個map中待德,用于在上層返回。

擴展點依賴注入

我們在返回上面枫夺,還又一點沒說将宪,就是依賴注入的功能injectExtension的代碼:

private T injectExtension(T instance) {
    try {
        // 在獲取第一個擴展點的ExtensionLoader的實例的時候,objectFactory就被實例化了橡庞,是AdaptiveExtensionFactory
        if (objectFactory != null) {
            // 遍歷要注入的實例的方法
            for (Method method : instance.getClass().getMethods()) {
                // 只處理set方法较坛,比如setA,就是要把A注入到instance中
                if (method.getName().startsWith("set")
                        && method.getParameterTypes().length == 1
                        && Modifier.isPublic(method.getModifiers())) {
                    // set方法參數(shù)類型
                    Class<?> pt = method.getParameterTypes()[0];
                    try {
                        // setter方法對應(yīng)的屬性名毙死,也就是擴展點接口名稱
                        String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";
                        /**
                         * objectFactory是AdaptiveExtensionFactory實例
                         * 比如這里的pt是com.alibaba.dubbo.rpc.Protocol燎潮,property是protocol
                         * objectFactory就會根據(jù)這兩個參數(shù)去獲取Protocol對應(yīng)的擴展實現(xiàn)的實例
                         */
                        Object object = objectFactory.getExtension(pt, property);
                        // 獲取到了setter方法的參數(shù)的實現(xiàn),可以進行注入
                        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;
}

依賴注入的代碼也很簡單扼倘,就是實例化要注入的類确封,然后反射調(diào)用set方法注入實例中去。

自適應(yīng)擴展點使用

到這里再菊,使用指定名稱加載擴展點實現(xiàn)的流程就分析完了爪喘,但是這種直接指定擴展點名字的方式卻不是我們主要使用的方式【腊危可以想象一下秉剑,dubbo是可以配置多協(xié)議的,也就是可以同時配置比如dubbo稠诲、rmi等協(xié)議侦鹏。如果我們使用了多協(xié)議的話诡曙,那dubbo是怎么做的呢?我們可以想到最簡單的方法就是有一個轉(zhuǎn)發(fā)器略水,用來根據(jù)實際請求中配置的協(xié)議來使用不同的實現(xiàn)來處理价卤,下面可以寫個偽代碼:

public class ProtocolDispatcher implements Protocol {
    
    public void refer(String name) {
        if (name.equals("dubbo")) {
            Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getExtension("dubbo");
        } else if(name.equals("rmi")) {
             Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getExtension("rmi");
        }
    }
}

實際上dubbo中沒有這樣的代碼,但實際上也差不多類似這樣的方式來處理的渊涝,我們看下實際在dubbo中的使用方式:

private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();

可以看到第一步還是先獲取Protocol類型的ExtensionLoader的實例慎璧,這個過程跟最上面的獲取ExtensionLoader實例的過程是一樣的,接下來這一步getAdaptiveExtension()就跟我們之前的示例不一樣了跨释,這是獲取自適應(yīng)擴展的方法胸私。

自適應(yīng)擴展是不是很熟悉,上面我們也說過自適應(yīng)鳖谈,可以回頭先去看下大概情況岁疼。首先說下獲取自適應(yīng)擴展是干嘛的?其實就是做到上面那個偽代碼的轉(zhuǎn)發(fā)器功能蚯姆。

自適應(yīng)擴展點動態(tài)生成的代碼

當(dāng)調(diào)用了上面getAdaptiveExtension()方法后五续,dubbo會動態(tài)生成如下代碼:

package com.alibaba.dubbo.rpc;

import com.alibaba.dubbo.common.extension.ExtensionLoader;

public class Protocol$Adpative implements com.alibaba.dubbo.rpc.Protocol {
    public void destroy() {
        throw new UnsupportedOperationException(
            "method public abstract void com.alibaba.dubbo.rpc.Protocol.destroy() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!"
        );
    }

    public int getDefaultPort() {
        throw new UnsupportedOperationException(
            "method public abstract int com.alibaba.dubbo.rpc.Protocol.getDefaultPort() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!"
        );
    }

    public com.alibaba.dubbo.rpc.Exporter export(com.alibaba.dubbo.rpc.Invoker arg0) throws com.alibaba.dubbo.rpc.Invoker {
        if (arg0 == null) 
            throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null");
        
        if (arg0.getUrl() == null)
            throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument getUrl() == null");
        
        com.alibaba.dubbo.common.URL url = arg0.getUrl();
        String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
        
        if (extName == null)
            throw new IllegalStateException(
                "Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])"
            );
        
        com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) 
            ExtensionLoader
                .getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class)
                .getExtension(extName);
        return extension.export(arg0);
    }

    public com.alibaba.dubbo.rpc.Invoker refer(java.lang.Class arg0, com.alibaba.dubbo.common.URL arg1) throws java.lang.Class {
        if (arg1 == null) 
            throw new IllegalArgumentException("url == null");
        
        com.alibaba.dubbo.common.URL url = arg1;
        String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
        
        if (extName == null)
            throw new IllegalStateException(
                "Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])"
            );
        
        com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) 
            ExtensionLoader
                .getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class)
                .getExtension(extName);
        return extension.refer(arg0, arg1);
    }
}

當(dāng)我們調(diào)用protocol.xxxx()方法的時候,其實就是調(diào)用動態(tài)生成的Protocol$Adaptive這個類的方法龄恋,這里面的邏輯其還是就跟我們的偽代碼差不多了疙驾,根據(jù)url中傳入的Protocol名字,通過getExtension(extName)方法獲取實際的擴展點實現(xiàn)實例郭毕。

自適應(yīng)擴展點的獲取

接下來就看下獲取自適應(yīng)擴展的源碼:

public T getAdaptiveExtension() {
    // 先從自適應(yīng)實例緩存中查找實例對象
    Object instance = cachedAdaptiveInstance.get();
    // 緩存中不存在
    if (instance == null) {
        if(createAdaptiveInstanceError == null) {
            synchronized (cachedAdaptiveInstance) {
                // 獲取鎖之后再檢查一次緩存中是不是已經(jīng)存在
                instance = cachedAdaptiveInstance.get();
                if (instance == null) {
                    try {
                        // 緩存中沒有它碎,就創(chuàng)建新的AdaptiveExtension實例
                        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)建自適應(yīng)擴展實例,繼續(xù)看createAdaptiveExtension()方法:

private T createAdaptiveExtension() {
    try {
        /**
         * 先通過getAdaptiveExtensionClass獲取自適應(yīng)擴展類的Class
         * 然后通過反射獲取實例
         * 最后如果自適應(yīng)擴展依賴了其他的擴展點乘碑,就進行擴展點注入
         */
        return injectExtension((T) getAdaptiveExtensionClass().newInstance());
    } catch (Exception e) {
        throw new IllegalStateException("Can not create adaptive extenstion " + type + ", cause: " + e.getMessage(), e);
    }
}

這里的邏輯跟createExtension()差不多挖息,大概步驟:

  • 先通過getAdaptiveExtensionClass()方法獲取自適應(yīng)擴展類的Class
  • 然后通過反射獲取實例
  • 最后如果自適應(yīng)擴展類實例依賴了其他的擴展點,就進行擴展點的注入

獲取自適應(yīng)擴展點類的Class

首先看下獲取自適應(yīng)擴展類的Class方法:

private Class<?> getAdaptiveExtensionClass() {
    /**
     * getExtensionClasses加載當(dāng)前擴展點的所有實現(xiàn)
     * 比如:
     * 我們在使用ExtensionLoader.getExtensionLoader(Protocol.class)
     * 獲取Protocol的ExtensionLoader的時候兽肤,就已經(jīng)設(shè)置了當(dāng)前ExtensionLoader
     * 的類型是Protocol的套腹,所以這里獲取的時候就是Protocol的所有實現(xiàn)。
     *
     * 獲取到所有的實現(xiàn)之后资铡,getExtensionClasses()返回的是Map<String, Class<?>>
     *
     * 另外需要說的是电禀,如果擴展點的實現(xiàn)注解了類級別的@Adaptive注解,
     * 這些實現(xiàn)的Class加載完后會賦值給cachedAdaptiveClass緩存笤休。如果擴展點的實現(xiàn)
     * 是包裝類尖飞,這些實現(xiàn)的Class加載完后會放到cachedWrapperClasses緩存中。
     * 其他的正常的擴展點的實現(xiàn)都會放到Map<String, Class<?>>中返回。
     *
     * 目前只有AdaptiveExtensionFactory和AdaptiveCompiler兩個實現(xiàn)類是被注解了@Adaptive
     * 也就是說這兩個就是自適應(yīng)擴展政基,如果要加載ExtensionFactory和Compiler的自適應(yīng)擴展
     * 不需要使用自動生成代碼贞铣,而是直接使用兩個實現(xiàn)類就可以了。
     * 其他的擴展點如果想要獲取自適應(yīng)擴展實現(xiàn)沮明,就需要繼續(xù)往下走咕娄,使用生成的Xxx$Adaptive代碼。
     *
     * 一個擴展點有且只有一個自適應(yīng)擴展點珊擂,要么是內(nèi)置的兩個AdaptiveExtensionFactory和AdaptiveCompiler,
     * 要么是生成的Xxx$Adaptive
     */
    getExtensionClasses();
    /**
     * 自適應(yīng)擴展實現(xiàn)费变,在上面一步加載的時候摧扇,就會被加載緩存起來
     * 只會執(zhí)行一次,后面再獲取的時候挚歧,就是獲取緩存起來的這個扛稽。
     */
    if (cachedAdaptiveClass != null) {
        return cachedAdaptiveClass;
    }
    // 沒有緩存自適應(yīng)擴展實現(xiàn),就動態(tài)創(chuàng)建一個
    return cachedAdaptiveClass = createAdaptiveExtensionClass();
}

獲取自適應(yīng)擴展類的過程參考上面代碼的注釋即可滑负,繼續(xù)往下說創(chuàng)建自適應(yīng)擴展類的方法createAdaptiveExtensionClass()

private Class<?> createAdaptiveExtensionClass() {
    /**
     * 根據(jù)具體的接口來生成自適應(yīng)擴展類的代碼
     * 比如Protocol就會生成Protocol$Adaptive為名字的類的代碼
     */
    String code = createAdaptiveExtensionClassCode();
    // 獲取類加載器
    ClassLoader classLoader = findClassLoader();
    // 獲取Compiler的自適應(yīng)擴展在张,獲取到的是AdaptiveCompiler實例
    com.alibaba.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
    // 如果我們沒有指定名字,默認(rèn)使用javassist
    return compiler.compile(code, classLoader);
}

這里大概的步驟是:

  • 生成自適應(yīng)擴展類的代碼矮慕。
  • 獲取類加載器帮匾。
  • 獲取自適應(yīng)的Compiler的擴展實現(xiàn),獲取到的AdaptiveCompiler實例痴鳄,這個在上面已經(jīng)說過了瘟斜。
  • 最后使用具體的Compiler進行生成代碼的編譯。

這里只看第一步痪寻,生成自適應(yīng)擴展類的代碼這步螺句,這里代碼有點長,不在此貼出來了橡类,參考我的github上ExtensionLoader的源碼注釋ExtensionLoader.java蛇尚。

@Adaptive注解

這里說下@Adaptive注解,有兩種地方使用這個注解:

  • 使用在實現(xiàn)類上
  • 使用在接口的方法上

這兩種不能重復(fù)使用顾画。如果用在實現(xiàn)類上取劫,一個擴展點的實現(xiàn)類有且只能有一個類使用此注解,比如ExtensionFactory的實現(xiàn)類AdaptiveExtensionFactory使用了此注解亲雪,這個類本身就是一個自適應(yīng)擴展類了勇凭;如果用在接口的方法上,表示dubbo框架會在生成該接口的自適應(yīng)擴展類的時候义辕,生成該方法的代碼虾标,如果方法沒有添加此注解,則生成拋出不支持異常的代碼。

ExtensionFactory

到這里獲取擴展和獲取自適應(yīng)擴展就已經(jīng)說完了璧函,接下來可以把最上面留下的ExtensionFactory相關(guān)的加載流程說下了傀蚌,每個ExtensionLoader實例中都會有一個objectFactory實例,而objectFactory實例的賦值都是在ExtensionLoader的構(gòu)造方法中:

private ExtensionLoader(Class<?> type) {
    this.type = type;
    /**
     * 對于擴展類型是ExtensionFactory的蘸吓,設(shè)置為null
     * getAdaptiveExtension方法獲取一個運行時自適應(yīng)的擴展類型
     * 每個Extension只能有一個@Adaptive類型的實現(xiàn)善炫,如果么有,dubbo會自動生成一個類
     * objectFactory是一個ExtensionFactory類型的屬性库继,主要用于加載擴展的實現(xiàn)
     */

    objectFactory = (
            type == ExtensionFactory.class ?
                    null :
                    ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension()
    );
}

可以看到ExtensionFactory的實例獲取也是通過擴展點自適應(yīng)來獲取到的箩艺,獲取到的實例是AdaptiveExtensionFactory。而在AdaptiveExtensionFactory實例化的時候宪萄,會通過SPI機制加載所有的ExtensionFactory的實現(xiàn):

public AdaptiveExtensionFactory() {
    ExtensionLoader<ExtensionFactory> loader = ExtensionLoader.getExtensionLoader(ExtensionFactory.class);
    List<ExtensionFactory> list = new ArrayList<ExtensionFactory>();
    for (String name : loader.getSupportedExtensions()) {
        // 保存所有ExtensionFactor y的實現(xiàn)
        list.add(loader.getExtension(name));
    }
    factories = Collections.unmodifiableList(list);
}

使用objectFactory獲取擴展的時候艺谆,是調(diào)用AdaptiveExtensionFactory的getExtension方法,該方法會遍歷所有的ExtensionFactory的實現(xiàn)的getExtension方法:

public <T> T getExtension(Class<T> type, String name) {
    // 依次遍歷各個ExtensionFactory實現(xiàn)的getExtension方法
    // 找到Extension后立即返回拜英,沒找到返回null
    for (ExtensionFactory factory : factories) {
        T extension = factory.getExtension(type, name);
        if (extension != null) {
            return extension;
        }
    }
    return null;
}

共兩種實現(xiàn)SpiExtensionFactory和SpringExtensionFactory静汤,如果在任何一個實現(xiàn)中找到了擴展點實現(xiàn),就返回結(jié)束了居凶。

dubbo是自包含的虫给,這個概念通過上面的解析也應(yīng)該不難理解了。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末侠碧,一起剝皮案震驚了整個濱河市抹估,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌舆床,老刑警劉巖棋蚌,帶你破解...
    沈念sama閱讀 217,185評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異挨队,居然都是意外死亡谷暮,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評論 3 393
  • 文/潘曉璐 我一進店門盛垦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來湿弦,“玉大人,你說我怎么就攤上這事腾夯〖瞻#” “怎么了?”我有些...
    開封第一講書人閱讀 163,524評論 0 353
  • 文/不壞的土叔 我叫張陵蝶俱,是天一觀的道長班利。 經(jīng)常有香客問我,道長榨呆,這世上最難降的妖魔是什么罗标? 我笑而不...
    開封第一講書人閱讀 58,339評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上闯割,老公的妹妹穿的比我還像新娘彻消。我一直安慰自己,他們只是感情好宙拉,可當(dāng)我...
    茶點故事閱讀 67,387評論 6 391
  • 文/花漫 我一把揭開白布宾尚。 她就那樣靜靜地躺著,像睡著了一般谢澈。 火紅的嫁衣襯著肌膚如雪煌贴。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,287評論 1 301
  • 那天锥忿,我揣著相機與錄音崔步,去河邊找鬼。 笑死缎谷,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的灶似。 我是一名探鬼主播列林,決...
    沈念sama閱讀 40,130評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼酪惭!你這毒婦竟也來了希痴?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,985評論 0 275
  • 序言:老撾萬榮一對情侶失蹤春感,失蹤者是張志新(化名)和其女友劉穎砌创,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體鲫懒,經(jīng)...
    沈念sama閱讀 45,420評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡嫩实,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,617評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了窥岩。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片甲献。...
    茶點故事閱讀 39,779評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖颂翼,靈堂內(nèi)的尸體忽然破棺而出晃洒,到底是詐尸還是另有隱情,我是刑警寧澤朦乏,帶...
    沈念sama閱讀 35,477評論 5 345
  • 正文 年R本政府宣布球及,位于F島的核電站,受9級特大地震影響呻疹,放射性物質(zhì)發(fā)生泄漏先舷。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,088評論 3 328
  • 文/蒙蒙 一栅炒、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧惶翻,春花似錦、人聲如沸鹅心。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽旭愧。三九已至颅筋,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間输枯,已是汗流浹背议泵。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留桃熄,地道東北人先口。 一個月前我還...
    沈念sama閱讀 47,876評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像瞳收,于是被迫代替她去往敵國和親碉京。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,700評論 2 354

推薦閱讀更多精彩內(nèi)容