1. 程式人生 > >Dubbo的SPI機制(二)(Dubbo優化後的SPI實現)

Dubbo的SPI機制(二)(Dubbo優化後的SPI實現)

上一篇部落格:

https://blog.csdn.net/Dongguabai/article/details/84345871

相關部落格:

https://blog.csdn.net/Dongguabai/article/details/83754289

 

在Dubbo的官方文件中,有一部分是專門介紹SPI擴充套件實現:

Dubbo的SPI規範

大部分的思想都是和SPI是一樣,只是下面兩個地方有差異。

1. 需要在resources目錄下配置META-INF/dubbo或者META-INF/dubbo/internal或者META-INF/services,並基於SPI介面去建立一個檔案;

2. 檔名稱和介面全類名保持一致,檔案內容和Java的SPI有差異,內容是KEY對應Value;

Dubbo的SPI示例

下面以協議擴充套件為例:

結合官方文件,實現擴充套件介面:

(我這裡使用的Dubbo版本為2.5.3,與官網最新版的文件有所不一樣)

在dubboclient中實現Exporter介面:

根據規範,建立resources/MRTA-INF/dubbo資料夾,並建立以Exporter全類名為檔名的檔案:

檔案內容為:

執行測試程式碼:

丟擲了一個異常:

Exception in thread "main" java.lang.IllegalArgumentException: Extension type(interface com.alibaba.dubbo.rpc.Exporter) is not extension, because WITHOUT @SPI Annotation!
2018-11-24 16:25:25,920 INFO [com.alibaba.dubbo.common.logger.LoggerFactory] - using logger: com.alibaba.dubbo.common.logger.log4j.Log4jLoggerAdapter
	at com.alibaba.dubbo.common.extension.ExtensionLoader.getExtensionLoader(ExtensionLoader.java:115)
	at dongguabai.dubbo.App.main(App.java:21)

說是沒有使用@SPI註解,那麼先來看看這個註解:

/*
 * Copyright 1999-2011 Alibaba Group.
 *  
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *  
 *      http://www.apache.org/licenses/LICENSE-2.0
 *  
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.alibaba.dubbo.common.extension;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 擴充套件點介面的標識。
 * <p />
 * 擴充套件點宣告配置檔案,格式修改。<br />
 * 以Protocol示例,配置檔案META-INF/dubbo/com.xxx.Protocol內容:<br />
 * 由<br/>
 * <pre><code>com.foo.XxxProtocol
com.foo.YyyProtocol</code></pre><br/>
 * 改成使用KV格式<br/>
 * <pre><code>xxx=com.foo.XxxProtocol
yyy=com.foo.YyyProtocol
 * </code></pre>
 * <br/>
 * 原因:<br/>
 * 當擴充套件點的static欄位或方法簽名上引用了三方庫,
 * 如果三方庫不存在,會導致類初始化失敗,
 * Extension標識Dubbo就拿不到了,異常資訊就和配置對應不起來。
 * <br/>
 * 比如:
 * Extension("mina")載入失敗,
 * 當用戶配置使用mina時,就會報找不到擴充套件點,
 * 而不是報載入擴充套件點失敗,以及失敗原因。
 *
 * @author william.liangf
 * @author ding.lid
 * @export
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface SPI {

    /**
     * 預設擴充套件點名。
     */
	String value() default "";

}

由於我這裡實現的是Exporter介面,這個介面是沒有新增@SPI註解的:

接下來改為實現Protocol介面:

同樣的,根據規範,在resources/MRTA-INF/dubbo資料夾下建立以Protocol全類名為檔名的檔案:

檔案內容為:

再執行測試程式碼:

輸出結果,可以看到呼叫成功:

分析

在dubbo-common下有AdaptiveExtensionFactory和SpiExtensionFactory:

還有三個註解:

在dubbo-config-spring下有SpringExtensionFactory:

這三個類和三個註解是Dubbo Extension的主要結構:

  • AdaptiveExtensionFactory
  • SpiExtensionFactory
  • SpringExtensionFactory
  • @SPI
  • @Adaptive
  • @Activate

之前實現的Protocol介面,已經被標註了@SPI註解,代表是一個擴充套件點,預設key是dubbo:

在dubbo-rpc-dubbo中也能夠找到相應的實現:

內容為:

所以之前的測試程式碼中,還有這樣一個方法:

這個就是獲取預設擴充套件。可以簡單測試一下:

執行結果: 

的確預設埠即為20880:

在Protocol介面的export()和refer()方法上使用了@Adaptive註解:

/**
     * 暴露遠端服務:<br>
     * 1. 協議在接收請求時,應記錄請求來源方地址資訊:RpcContext.getContext().setRemoteAddress();<br>
     * 2. export()必須是冪等的,也就是暴露同一個URL的Invoker兩次,和暴露一次沒有區別。<br>
     * 3. export()傳入的Invoker由框架實現並傳入,協議不需要關心。<br>
     * 
     * @param <T> 服務的型別
     * @param invoker 服務的執行體
     * @return exporter 暴露服務的引用,用於取消暴露
     * @throws RpcException 當暴露服務出錯時丟擲,比如埠已佔用
     */
    @Adaptive
    <T> Exporter<T> export(Invoker<T> invoker) throws RpcException;

    /**
     * 引用遠端服務:<br>
     * 1. 當用戶呼叫refer()所返回的Invoker物件的invoke()方法時,協議需相應執行同URL遠端export()傳入的Invoker物件的invoke()方法。<br>
     * 2. refer()返回的Invoker由協議實現,協議通常需要在此Invoker中傳送遠端請求。<br>
     * 3. 當url中有設定check=false時,連線失敗不能丟擲異常,並內部自動恢復。<br>
     * 
     * @param <T> 服務的型別
     * @param type 服務的型別
     * @param url 遠端服務的URL地址
     * @return invoker 服務的本地代理
     * @throws RpcException 當連線服務提供方失敗時丟擲
     */
    @Adaptive
    <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException;

在ServiceConfig中是這麼使用的:

public class ServiceConfig<T> extends AbstractServiceConfig {

    private static final long   serialVersionUID = 3033787999037024738L;

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

 先看看getExtensionLoader()方法,這個方法就是根據一個Class去獲取一個擴充套件的Loader,程式碼也很簡單:

@SuppressWarnings("unchecked")
    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!");
        }
        if(!withExtensionAnnotation(type)) {
            throw new IllegalArgumentException("Extension type(" + type + 
                    ") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");
        }
        
        ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
        //沒有就new一個
        if (loader == null) {
            EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
            loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
        }
        //有就直接獲取
        return loader;
    }

就是根據型別去從EXTENSION_LOADERS中獲取Loader,這個EXTENSION_LOADERS就是一個 ConcurrentHashMap:

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

ExtensionLoader的私有的構造方法:

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

再看getAdaptiveExtension()方法,這個方法的作用是獲取一個自適應擴充套件點

總體來看這個方法:

會先從一個cacheAdaotiveInstance中獲取一個示例,如果不為null,直接返回,為null就生成instance後再set給cacheAdaotiveInstance,中間還使用的DCL機制:

首先會從cachedAdaptiveInstance中獲取一個instance:

看看這個cachedAdaptiveInstance:

是一個Holder例項。再看看Holder,主要作用是用來存值:

再接著看看getAdaptiveExtension()方法:

經過一系列的判斷後,如果instance為null,則呼叫createAdaptiveExtension()方法:

看看createAdaptiveExtension()方法:

先看getAdaptiveExtensionClass()方法:

先看createAdaptiveExtensionClass()方法:

//建立一個介面卡擴充套件點,基於動態位元組碼的方式編譯
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);
    }

執行之前的程式:debug看看:

code中的內容為:

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;
        //根據url引數去做判斷
        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);
    }
}

這裡需要結合Dubbo服務釋出的程式碼:

可以看到初始化的時候會去載入(final)這個介面卡的擴充套件點,也就是上面介紹的Protocol$Adpative,這樣一個動態的類檔案。

實際上是呼叫的Protocol介面的export()方法,而這個方法肯定是有很多是實現的:

在看看getExtensionClasses()方法,看名字就可以猜出來,主要是用來載入擴充套件點的實現類:

//載入擴充套件點的實現類
private Map<String, Class<?>> getExtensionClasses() {
       //以Protocol為例,會將Protocol所有的擴充套件實現放到Map中
        Map<String, Class<?>> classes = cachedClasses.get();
//DCL
        if (classes == null) {
            synchronized (cachedClasses) {
                classes = cachedClasses.get();
                if (classes == null) {
                       //如果Map為空,載入Map
                    classes = loadExtensionClasses();
                    cachedClasses.set(classes);
                }
            }
        }
        return classes;
	}

看下loadExtensionClasses()方法:

// 此方法已經getExtensionClasses方法同步過。
    private Map<String, Class<?>> loadExtensionClasses() {
        //這個type就是比如傳過來的Protocol.class
        final SPI defaultAnnotation = type.getAnnotation(SPI.class);
        if(defaultAnnotation != null) {
            //獲取@SPI註解中的資訊
            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];
            }
        }
        
        Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
//接下來載入Dubbo SPI中的擴充套件實現
//    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/";

        loadFile(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
        loadFile(extensionClasses, DUBBO_DIRECTORY);
        loadFile(extensionClasses, SERVICES_DIRECTORY);
        return extensionClasses;
    }

這個loadFile()方法很長,但是也能夠大致推測出會遍歷目錄下的檔案,載入相應的Class:

private void loadFile(Map<String, Class<?>> extensionClasses, String dir) {
    String fileName = dir + type.getName();
    try {
        Enumeration<java.net.URL> urls;
        ClassLoader classLoader = findClassLoader();
        if (classLoader != null) {
            urls = classLoader.getResources(fileName);
        } else {
            urls = ClassLoader.getSystemResources(fileName);
        }
        if (urls != null) {
            while (urls.hasMoreElements()) {
                java.net.URL url = urls.nextElement();
                try {
                    BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream(), "utf-8"));
                    try {
                        String line = null;
                        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) {//檔案採用name=value方式,通過i進行分割
                                        name = line.substring(0, i).trim();
                                        line = line.substring(i + 1).trim();
                                    }
                                    if (line.length() > 0) {
                                        Class<?> clazz = Class.forName(line, true, classLoader);
                                        //載入對應的實現類,並且判斷實現類必須是當前的載入的擴充套件點的實現
                                        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.");
                                        }

                                        //判斷是否有自定義適配類,如果有,則在前面講過的獲取適配類的時候,直接返回當前的自定義適配類,不需要再動態建立
// 還記得在前面講過的getAdaptiveExtensionClass中有一個判斷嗎?是用來判斷cachedAdaptiveClass是不是為空的。如果不為空,表示存在自定義擴充套件點。也就不會去動態生成位元組碼了。這個地方可以得到一個簡單的結論;
// @Adaptive如果是加在類上, 表示當前類是一個自定義的自適應擴充套件點
//如果是加在方法級別上,表示需要動態建立一個自適應擴充套件點,也就是Protocol$Adaptive
                                        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 {
                                            try {
                                                //如果沒有Adaptive註解,則判斷當前類是否帶有引數是type型別的建構函式,如果有,則認為是
                                                //wrapper類。這個wrapper實際上就是對擴充套件類進行裝飾.
                                                //可以在dubbo-rpc-api/internal下找到Protocol檔案,發現Protocol配置了3個裝飾
                                                //分別是,filter/listener/mock. 所以Protocol這個例項來說,會增加對應的裝飾器
                                                clazz.getConstructor(type);//
                                                //得到帶有public DubboProtocol(Protocol protocol)的擴充套件點。進行包裝
                                                Set<Class<?>> wrappers = cachedWrapperClasses;
                                                if (wrappers == null) {
                                                    cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();
                                                    wrappers = cachedWrapperClasses;
                                                }
                                                wrappers.add(clazz);//包裝類 ProtocolFilterWrapper(ProtocolListenerWrapper(Protocol))
                                            } catch (NoSuchMethodException e) {
                                                clazz.getConstructor();
                                                if (name == null || name.length() == 0) {
                                                    name = findAnnotationName(clazz);
                                                    if (name == null || name.length() == 0) {
                                                        if (clazz.getSimpleName().length() > type.getSimpleName().length()
                                                                && clazz.getSimpleName().endsWith(type.getSimpleName())) {
                                                            name = clazz.getSimpleName().substring(0, clazz.getSimpleName().length() - type.getSimpleName().length()).toLowerCase();
                                                        } else {
                                                            throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + url);
                                                        }
                                                    }
                                                }
                                                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());
                                                        }
                                                    }
                                                }
                                            }
                                        }
                                    }
                                } catch (Throwable t) {
                                    IllegalStateException e = new IllegalStateException("Failed to load extension class(interface: " + type + ", class line: " + line + ") in " + url + ", cause: " + t.getMessage(), t);
                                    exceptions.put(line, e);
                                }
                            }
                        } // end of while read lines
                    } finally {
                        reader.close();
                    }
                } catch (Throwable t) {
                    logger.error("Exception when load extension class(interface: " +
                                        type + ", class file: " + url + ") in " + url, t);
                }
            } // end of while urls
        }
    } catch (Throwable t) {
        logger.error("Exception when load extension class(interface: " +
                type + ", description file: " + fileName + ").", t);
    }
}

關於@Adaptive註解:

  • @Adaptive如果是加在類上, 表示當前類是一個自定義的自適應擴充套件點
  • 如果是加在方法級別上,表示需要動態建立一個自適應擴充套件點,也就是Protocol$Adaptive
/*
 * Copyright 1999-2011 Alibaba Group.
 *  
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *  
 *      http://www.apache.org/licenses/LICENSE-2.0
 *  
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.alibaba.dubbo.common.extension;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import com.alibaba.dubbo.common.URL;

/**
 * 在{@link ExtensionLoader}生成Extension的Adaptive Instance時,為{@link ExtensionLoader}提供資訊。
 * 
 * @author ding.lid
 * @export
 * 
 * @see ExtensionLoader
 * @see URL
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Adaptive {
    
    /**
     * 從{@link URL}的Key名,對應的Value作為要Adapt成的Extension名。
     * <p>
     * 如果{@link URL}這些Key都沒有Value,使用 用 預設的擴充套件(在介面的{@link SPI}中設定的值)。<br>
     * 比如,<code>String[] {"key1", "key2"}</code>,表示
     * <ol>
     * <li>先在URL上找key1的Value作為要Adapt成的Extension名;
     * <li>key1沒有Value,則使用key2的Value作為要Adapt成的Extension名。
     * <li>key2沒有Value,使用預設的擴充套件。
     * <li>如果沒有設定預設擴充套件,則方法呼叫會丟擲{@link IllegalStateException}。
     * </ol>
     * <p>
     * 如果不設定則預設使用Extension介面類名的點分隔小寫字串。<br>
     * 即對於Extension介面{@code com.alibaba.dubbo.xxx.YyyInvokerWrapper}的預設值為<code>String[] {"yyy.invoker.wrapper"}</code>
     * 
     * @see SPI#value()
     */
    String[] value() default {};
    
}

總的來說,getAdaptiveExtensionClass()方法獲取了Protocol$Adpative。主要做了兩個事情:

  1. getExtensionClasses() 載入所有路徑下的擴充套件點
  2. createAdaptiveExtensionClass() 動態建立一個擴充套件點

cachedAdaptiveClass這裡有個判斷,用來判斷當前Protocol這個擴充套件點是否存在一個自定義的介面卡,如果有,則直接返回自定義介面卡,否則,就動態建立,這個值是在getExtensionClasses中賦值的。

createAdaptiveExtensionClass()動態生成介面卡程式碼,以及動態編譯:

  1. createAdaptiveExtensionClassCode,  動態建立一個位元組碼檔案。返回code這個字串
  2. 通過compiler.compile進行編譯(預設情況下使用的是javassist)
  3. 通過ClassLoader載入到jvm中

發現很大一部分是與Adaptive相關,那這個自適應到底有什麼用呢?

自適應從字面意思理解是在執行的時候可以根據當前的狀態去執行或者生成符合當前場景的東東。

接下來重點看看Protocol$Adpative的refer()和export()方法,一個是引用一個是釋出:

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;
        //根據url引數去做判斷,如果是null,則預設使用dubbo協議
        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) 
//根據名稱獲取相應名稱的擴充套件帶點,比如hessian
ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
        return extension.refer(arg0, arg1);
    }
}

Protocol$Adaptive的主要功能

     1.從url或擴充套件介面獲取擴充套件介面實現類的名稱;

     2.根據名稱,獲取實現類ExtensionLoader.getExtensionLoader(擴充套件介面類).getExtension(擴充套件介面實現類名稱),然後呼叫實現類的方法。

需要明白一點dubbo的內部傳參基本上都是基於Url來實現的,也就是說Dubbo是基於URL驅動的技術。

所以,介面卡類的目的是在執行期獲取擴充套件的真正實現來呼叫,解耦介面和實現,這樣的話要不我們自己實現介面卡類,要不dubbo幫我們生成,而這些都是通過Adpative來實現。

到目前為止,我們的AdaptiveExtension的主線走完了,可以簡單整理一下他們的呼叫關係如下:

也就是說,之前的這段程式碼:

​Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class). getAdaptiveExtension();

 最終的protocol等於Protocol$Adaptive。