Dubbo解析(二)-內核實現之SPI機制(下)

上一章咱們介紹了JDK的SPI機制,它旨在創建一種服務發現的規範。而Dubbo基於此根據框架的總體設計作了一些改進:java

  1. JDK的SPI機制會一次性實例化全部服務提供者實現,若是有提供者的初始化很耗時,但並不會使用會很耗費資源。Dubbo則只存儲了全部提供者的Class對象,實際使用時才構造對象
  2. JDK的SPI機制只在配置文件中記錄了實現類的全限定名,並無定義一個配置名。而Dubbo的服務接口每每提供多個實現方式,須要在每一個服務接口中定義一個匹配方法去選擇要使用哪一種實現方式。這種方式不利於框架的擴展,於是規定在提供者實現類的配置文件中對每一個實現類定義一個配置名,用"="隔開,造成key-value的方式。
  3. 增長了對服務接口的IOC和AOP的支持,服務接口內的其餘服務接口成員直接經過SPI的方式加載注入。

Dubbo做爲一個微內核+插件的框架設計,其內核就是基於SPI機制動態地組裝插件。插件都是以接口的方式定義在框架中,每一個接口提供的服務也稱爲擴展點,表示每一個服務接口均可以根據不一樣的條件進行動態擴展,Dubbo的擴展點接口以@SPI註解標識。Dubbo自身的功能也是經過擴展點實現的,也就是Dubbo的全部功能點均可以被用戶自定義擴展所代替。那麼Dubbo的SPI機制到底是怎麼工做的呢?app

1.SPI約定

在classpath下放置META-INF/dubbo/以接口全限定名定義的文件,內容爲配置名=擴展實現類全限定名,多個實現類用換行符分隔。框架

META-INF/dubbo目錄是針對二次開發者的,dubbo自身加載擴展點配置的目錄有三個,依次是:ide

  1. META-INF/dubbo/internal/
  2. META-INF/dubbo/
  3. META-INF/services/

以Dubbo的協議接口Protocol爲例,在dubbo-rpc-default模塊中定義了DubboProtocol實現,目錄爲url

META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Protocol

文件中內容爲spa

dubbo=com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol

對應於dubbo的xml配置,即.net

<dubbo:protocol name="dubbo" />

2.擴展點適配器

JDK自帶的SPI機制使用ServiceLoader加載和獲取,Dubbo對於擴展點的的加載和獲取則是使用ExtensionLoader。大部分的擴展點都是經過ExtensionLoader預加載一個適配類,以Protocol爲例,調用方式以下:插件

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

getAdaptiveExtension方法建立擴展點的適配類,建立的方式分爲兩種設計

  1. 若是有且僅有一個實現類上有@Adaptive註解,則直接由其Class對象的newInstance方法建立適配類對象
  2. 若是不存在類上有@Adaptive註解的實現類,則使用字符串拼接類名爲接口名$Adaptive的Class代碼,默認使用javassist編譯生成Class對象,而後使用newInstance方法建立適配類對象。

對於1的方式很好理解,好比Compiler接口,它的實現類AdaptiveCompiler類上就有@Adaptive註解netty

@Adaptive
public class AdaptiveCompiler implements Compiler {

    // 實現代碼省略

}

2的方式就較爲複雜,仍是以Protocol舉例,先看下Protocol接口的源碼:

@SPI("dubbo")
public interface Protocol {

    int getDefaultPort();

    @Adaptive
    <T> Exporter<T> export(Invoker<T> invoker) throws RpcException;

    @Adaptive
    <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException;

    void destroy();

}

Protocol接口定義了四個方法,其中export和refer方法都有@Adaptive註解,不一樣於1中類上的註解,這裏的註解是在方法上,這個地方要着重強調一下@Adaptive註解做用的位置。對於實現類上沒有此註解,而方法上有此註解的,適用於2方式。Protocol基於2方式生成的代碼以下:

package com.alibaba.dubbo.rpc;

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


public class Protocol$Adaptive implements com.alibaba.dubbo.rpc.Protocol {
    // 沒有@Adaptive註解的直接拋出UnsupportedOperationException
    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!");
    }

    // 有@Adaptive的方法,從參數裏獲取URL對象,
    // 獲取擴展點配置名,ExtensionLoader.getExtension(extName)匹配建立擴展點實現類對象
    public com.alibaba.dubbo.rpc.Invoker refer(java.lang.Class arg0,
        com.alibaba.dubbo.common.URL arg1)
        throws com.alibaba.dubbo.rpc.RpcException {
        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);
    }

    public com.alibaba.dubbo.rpc.Exporter export(
        com.alibaba.dubbo.rpc.Invoker arg0)
        throws com.alibaba.dubbo.rpc.RpcException {
        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);
    }
}

生成的適配類對象,以擴展點接口+$Adaptive命名(如Protocol$Adaptive),實現了擴展點接口。沒有@Adaptive的方法直接拋出UnsupportedOperationException,對於有@Adaptive的方法,從參數中獲取URL對象,而後根據URL獲取擴展點配置名,再使用ExtensionLoader.getExtension(extName)匹配建立擴展點實現類對象。

這裏的URL是Dubbo自定義的類,它是Dubbo的統一數據模型,穿插於系統的整個執行過程。URL的數據格式以下

dubbo://192.168.2.100:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-consumer&check=false&dubbo=2.0.0&generic=false&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&owner=william&pid=7356&side=consumer&timestamp=1416971340626

a. URL獲取擴展點配置名,經過key從URL的parameter中獲取

String extName = url.getParameter(key, defalutValue);

defalutValue取值於擴展點接口的@SPI的值,好比Protocol接口的defaultValue=dubbo

@SPI("dubbo")
public interface Protocol {}

key的值來自於@Adaptive的value值,若是@Adaptive沒有設置value,默認爲擴展點接口的類的simpleName。若是key的值爲protocol時特殊處理:

String extName = url.getProtocol()

好比Transporter擴展點的bind方法

@SPI("netty")
public interface Transporter {

    @Adaptive({"server", "transporter"})
    Server bind(URL url, ChannelHandler handler) throws RemotingException;
}

defaultValue=netty,key=server,transporter,獲取配置名的方法:

String extName = url.getParameter("server",url.getParameter("transporter", "netty"));

b. 擴展點建立完$Adaptive對象,具體調用時,根據URL參數獲取的配置名extName,查詢對應的擴展點實現,仍是以Protocol擴展點舉例

ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);

getExtension(extName)方法匹配擴展點的實現類Class的配置名,找到對應的Class對象,執行newInstance方法建立實際的操做對象。extName=dubbo的Protocol擴展點實現類爲DubboProtocol。建立完實現類對象,會對此對象執行injectExtension方法,即對對象內的以set開頭,而且只有一個參數的public方法,執行IOC注入。

IOC注入也是以ExtensionFactory擴展點的方式實現,默認的方式是先以SPI機制獲取方法參數類型的實現,若是此方法參數類型非接口或沒有@SPI註解,則從Spring上下文中獲取。

if (objectFactory != null) {
    for (Method method : instance.getClass().getMethods()) {
        if (method.getName().startsWith("set")
                && method.getParameterTypes().length == 1
                && Modifier.isPublic(method.getModifiers())) {
            Class<?> pt = method.getParameterTypes()[0];
            try {
                String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";
                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);
            }
        }
    }
}

接下來判斷擴展點的實現類中是否存在包裝類(Wrapper),Wrapper類指存在參數爲擴展點接口的構造方法的擴展點實現類。Wrapper類不是擴展點的真正實現,主要用於對真正的擴展點實現進行包裝。好比Protocol的Filter包裝類實現ProtocolFilterWrapper。

若是擴展點存在包裝類的Class,反射調用其構造方法,將真正的擴展點實現傳入,並再此執行injectExtension方法進行IOC注入。

Set<Class<?>> wrapperClasses = cachedWrapperClasses;
if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
    for (Class<?> wrapperClass : wrapperClasses) {
        instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
    }
}

至此,擴展點的調用來到了真正對應的擴展點實現類對象中。

3.擴展點自動激活

若是須要同時加載多個擴展點實現,可使用自動激活。當擴展點的實現類上有@Activate註解,標識它是一個激活類。@Activate能夠配置group和value值,ExtensionLoader的getActivateExtension方法匹配並獲取符合條件的全部激活類。Dubbo內部使用激活類的主要在Protocol的Filter和Listener,後面再詳細介紹。

4.配置文件的加載

以上討論的擴展點的適配類,包裝類,激活類的實現,都要基於對SPI機制的配置文件的加載。Dubbo在獲取擴展點的任何擴展時,都會先判斷是否加載了配置文件,若是沒有,即執行loadExtensionClasses方法加載,loadExtensionClasses方法中對Dubbo的三個配置目錄分別加載,調用loadFile方法。

loadFile方法查找目錄下擴展點接口的全限定名的文件,過濾掉"#"的註釋內容,而後每行以"="分隔,獲取配置名和擴展點實現類的全限定名。

依次判斷是否爲Adaptive適配類,是否爲Wrapper包裝類,是否爲Activate激活類,並存儲到對應的變量或集合中去。詳細操做可見ExtensionLoader.loadFile方法。

最後貼上getExtension(name)方法的總體活動圖(右擊圖片打開新標籤頁查看大圖),來自大神的博客https://blog.csdn.net/quhongwei_zhanqiu/article/details/41577235

image

相關文章
相關標籤/搜索