Dubbo源碼分析 ---- 基於SPI的擴展實現機制

Dubbo源碼分析–基於SPI的可擴展框架
dubbo是阿里巴巴開源出來的一套分佈式服務框架,該框架可以比較方便的實現分佈式服務的開發,調用等,該框架的教程地址爲
http://dubbo.io/Home-zh.htm
代碼已經託管到github上。
正好項目裏使用了一套網關的框架來做分佈式服務開發,該網關的框架是在dubbo的基礎上改造而來的,改dubbo默認的基於netty的分佈式服務調用爲將請求寫入到redis隊列中,而在處理方則訂閱對應的隊列,處理完成後,再將結果寫回到redis中,從而返回給客戶端,由於使用了redis來作爲隊列,因此可以起到一定的緩衝的作用,起到一定程度上的削峯填谷。
由於該框架是在dubbo的基礎上開發的,利用閒暇的時間下載了dubbo的源碼大致的翻了一遍,結合dubbo的用戶手冊,看了一遍看得不明所以;正好最近處於項目的間隙期,因此仔細的研讀了一遍代碼,感覺收穫頗多。。下面簡單的記錄下自己關於SPI方面的理解。

dubbo的SPI機制是在標準的jdk的SPI的機制上擴展加強而來的
SPI的實現在dubbo中由以下幾個annotation來實現。
1. SPI 註解,,使用SPI註解來標識一個擴展點,該註解一般是打在接口上的,DUBBO的擴展點都是基於接口的。
2. Adaptive註解 該註解主要作用在方法上,使用該註解可以根據方法的參數值來調用的具體的實現類的對應方法
3. Activate 註解,該註解一般作用在實現類上,使用該註解一般是對於Filter類型的類,來決定是該類是否加入到Filter的執行器責任鏈中

SPI機制實現的核心類
ExtensionLoader, 該類是SPI機制實現的核心類,該類提供了以下靜態方法getExtensionLoader(Class type),該方法返回了加載此class的ExtensionLoader, 通過該ExtensionLoader來創建對應的type的類的實例,
該類還提供了以下方法來獲取對應的加載類的實例
1. getAdaptiveExtension(String name)
通過該方法來獲取該type的具有adaptive過的擴展實現,也即是調用返回的實例的方法的時候,如果方法上有Adaptive註解,則會在方法調用的時候纔會根據方法的參數調用到對應的實例,
該方法一般用在一些高層次的代理實現中,如ReferenceConfig中包含有如下定義
private static final Protocol refprotocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();

Protocol有很多個實現類,但是ReferenceConfig作爲服務提供方的一個接口實例,此時並不知道該調用哪個實現類,當生成一個refprotocol來代表所有的實現類,當調用到該類中的export方法的時候,再根據方法的參數來決定調用到哪個具體的實現類中,相當於一種高級別的代理。

這種模式是如何實現的呢,
查看getAdaptiveExtend的實現類發現最終調用到
createAdaptiveExtensionClassCode方法來創建該接口的具體的Class的實例,通過該Class實例來創建對應的類實例。而createAdaptiveExtensionClassCode的方法可以看到,在方法中使用java代碼拼出了一個類, 在該方法主要是根據傳入的接口,生成一個該接口的實現類, 而在實現類中主要根據接口中的方法中是否有Adaptive註解,如果有該註解則對生成一個該方法的代理方法,如果沒有Adaptive註解,這實現的方法中拋出異常,如果有Adaptive註解則生成對應的代理後的方法,,如Protocol接口的代理類爲如下

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

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

如上代碼中export方法是經過Adaptive標註過的方法,該方法的實現如下

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

在該方法中首先獲取到protocol的值, 如果沒有獲取到則取默認值dubbo,再調用ExtensionLoader類中的getExtensionLoader(Interface.classs).getExtension(extName),方法加載到對應的擴展類的實現,最後調用該實例的export方法。

在生成了該類的代碼後, 在調用Compile的擴展類的實例的compile方法,將該類代碼編譯爲對應的Class對象。

2. public T getExtension(String name) 方法
該方法返回對應的擴展類的實例,方法根據傳入的name值來獲取到該name值對應的該類的實例,
通過該方法的實現分析可知,該方法中調用createExtension方法來生成對應的擴展類的實例,在createExtension方法中調用getExtensionClasses方法獲取該接口的所有的擴展類的實例, 在根據傳入的name值獲取到該值對應的擴展類的Class實例, 而getExtensionClasses方法的實現可以看到該方法調用loadFile方法依次從”META-INF/dubbo/internal/, META-INF/dubbo/, META-INF/services/”目錄下加載以該類的全額限定名的文件,再讀取該文件中的所有的key-value的值
loadFile方法的實現感覺比較複雜, 主要是函數的深度太深了。。嵌套了很多層的邏輯, 一般我司要求函數的深度都不能超過5層,但是此函數深度將近20,,看的十分費勁,不知道阿里是如何保證如此複雜的邏輯的代碼可測試性的。。
該方法主要完成以下幾個功能
2.1 加載對應的文件中定義的所有的類,且要求該類的所有實現類中只能有一個Adaptive類型的類,
2.2 如果該實現類包含有該接口類型的構造函數,則將該類加入到包裝器類的set中
2.3 如果該類的實現類中沒有該接口類型的構造函數, 則檢查該類是否又Activate註解,如果有該註解則將該類加入到註解的緩存中, 同時將該類的擴展的名字和Class對象加入到緩存中。

在根據name值加載完成對應擴展類的Class對象後,使用反射實例化出來一個該類的實例, 然後將該實例加入到緩存的Map中, 在生成該類的實例後,還要執行一步注入的流程,也即是injectExtension的步驟, 是通過調用injectExtension方法來實現的, 該方法通過獲取該類中的所有set方法,通過調用該方法進行屬性的注入,該方法的實現如下

private T injectExtension(T instance) {
        try {
            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);
                        }
                    }
                }
            }
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
        return instance;
    }

在該段代碼中最主要的一段是
Object object = objectFactory.getExtension(pt, property);
通過該方法的調用,可以獲取到對應的set方法的要注入的屬性的值,從而獲取到該值,將該值注入到生成的對象中。

如在RegistryProtocol類中由如下的代碼片段

    private Cluster cluster;

    public void setCluster(Cluster cluster) {
        this.cluster = cluster;
    }

    private Protocol protocol;

    public void setProtocol(Protocol protocol) {
        this.protocol = protocol;
    }

則在生成RegistryProtocol的實例的時候,會注入cluster的實例和protocol的實例,那麼該到那兒去找這些對象的實例呢,,,是通過objectFactory中加載, 加載的類型爲參數的類型,如Protocol,加載的參數爲set後的方法名字的內容,如setProtocol方法加載的值爲protocol。。

在inject實例完成後,還要再進行最後一步,在getFile方法的分析中我們可知,會生成每個對象的包裝器類, 也即使2.2中定義的對象, 則此時需要判斷包裝器類是否爲空,如果不爲空,則需要生成對應的包裝器類型,而不是原類型,同時也要對生成的包裝器類型進行再一次的setMethod的注入。。
至此類的實例化完成。

我們就可以在業務代碼中調用ExtensionLoader的getExtension方法獲取到該接口的基於參數名的對應的實現類,通過調用getAdaptiveExtension方法獲取到使用Compiler編譯後的代理類的對象。。這樣就可以調用對應的對象的方法來完成對應的業務邏輯

該方法的調用邏輯爲
getExtension->createExtension-> getExtensionClasses-> loadFile
-> injectExtension

下一篇將分析dubbo支持的幾種基本的協議

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章