dubbo分析(1)-插件化模塊化能力類SPI能力設計解析

基於dubbo 2.7 版本

都說duboo的插件化是基於spi我並不認同着一個觀點。 我認爲dubbo是本身實現了一波spi相似spring bootjava

總結:dubbo 並無使用java的spi而是實現了一種更增強悍的spi機制(自動類加載機制)spring

核心類ExtensionLoader

dubbo 本身實現SPI機制

其實這一塊的邏輯很簡單,定位到了核心類ExtensionLoader的兩個方法中apache

public T getExtension(String name) {
    if (name == null || name.length() == 0)
        throw new IllegalArgumentException("Extension name == null");
    if ("true".equals(name)) {
        // 獲取默認的拓展實現類
        return getDefaultExtension();
    }
    // Holder,顧名思義,用於持有目標對象
    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) {
                // 建立拓展實例
                instance = createExtension(name);
                // 設置實例到 holder 中
                holder.set(instance);
            }
        }
    }
    return (T) instance;
}

第二個核心方法 createExtension 這個是真正建立類型的方法數組

@SuppressWarnings("unchecked")
private T createExtension(String name) {
    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);
        }
        injectExtension(instance);
        Set<Class<?>> wrapperClasses = cachedWrapperClasses;
        if (CollectionUtils.isNotEmpty(wrapperClasses)) {
            for (Class<?> wrapperClass : wrapperClasses) {
                instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
            }
        }
        initExtension(instance);
        return instance;
    } catch (Throwable t) {
        throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
                type + ") couldn't be instantiated: " + t.getMessage(), t);
    }
}

自適應擴展

這個類在整個dubbo中算是一個硬核的類了,總計1000多行代碼,看名稱就能知道這個類其實承載了dubbo整個動態加載的邏輯緩存

Extension在dubbo的使用方法通常是這樣的app

Xxx xxx = ExtensionLoader.getExtensionLoader(Xxx.class).getAdaptiveExtension();

這種方法獲取的是這個類的自定義擴展信息,而且帶上了參數校驗 , 注意必須是被spi註解標記的類才能夠進行使用jvm

核心其實就是兩個方法,getExtensionLoader和getAdaptiveExtension,前者負責實例化一個ExtensionLoader 後者進行動態加載函數

getExtensionLoader

這個方法很簡單,就是將類型和ExtensionLoder對象之間的映射關係一一對應,而且初始化ExtensionLoader對象中的ObjectFactory 類型對象實例化工程方法ui

public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
    //獲取擴展
    ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
    if (loader == null) {
        //建立而且賦值 擴展加載器
        EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
        loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
    }
    return loader;
}

class ExtensionLoader{
    private ExtensionLoader(Class<?> type) {
        this.type = type;
        //這裏初始化 類實例化工廠方法, 默認使用ExtensionFactory實現
        objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
    }
}

getAdaptiveExtension

這個類就是很是複雜了,我邏輯簡化一下,只作關鍵點記錄this

public T getAdaptiveExtension() {
    //獲取緩存的自適應實例 ()
    Object instance = cachedAdaptiveInstance.get();
    if (instance == null) {
        try {
            //我擦。。。居然是在createAdaptiveExtension中進行建立的,繼續跟蹤
            instance = createAdaptiveExtension();
            cachedAdaptiveInstance.set(instance);
        } catch (Throwable t) {
            createAdaptiveInstanceError = t;
            throw new IllegalStateException("Failed to create adaptive instance: " + t.toString(), t);
        }
    }
    return (T) instance;
}
發現一個關鍵方法createAdaptiveExtension
private T createAdaptiveExtension() {
    try {
        return injectExtension((T) getAdaptiveExtensionClass().newInstance());
    } catch (Exception e) {
        throw new IllegalStateException("Can't create adaptive extension " + type + ", cause: " + e.getMessage(), e);
    }
}

有點噁心了 先看看getAdaptiveExtensionClass方法幹嗎了吧

private Class<?> getAdaptiveExtensionClass() {
    getExtensionClasses();
    if (cachedAdaptiveClass != null) {
        return cachedAdaptiveClass;
    }
    return cachedAdaptiveClass = createAdaptiveExtensionClass();
}
有一個方法 getExtensionClasses , 不過這個方法很重要 , 下面的全部的邏輯都是爲了加載擴展類的並緩存在本身的內存數組上

不看源碼了 , 直接說結論 從一個指定的文件夾中的文件中獲取像下面這樣文本

adaptive=org.apache.dubbo.common.compiler.support.AdaptiveCompiler
jdk=org.apache.dubbo.common.compiler.support.JdkCompiler
javassist=org.apache.dubbo.common.compiler.support.JavassistCompiler

將這個文本整合出一套k v 結構的map 注意一種註解和一種類型@Adaptive和WrapperClass(ps 以當前類型爲構造函數的類)

getAdaptiveExtensionClass 方法一樣包含了三個邏輯,以下:

調用 getExtensionClasses 獲取全部的拓展類
檢查緩存,若緩存不爲空,則返回緩存
若緩存爲空,則調用 createAdaptiveExtensionClass 建立自適應拓展類
這三個邏輯看起來平淡無奇,彷佛沒有多講的必要。可是這些平淡無奇的代碼中隱藏了着一些細節,須要說明一下。首先從第一個邏輯提及,getExtensionClasses 這個方法用於獲取某個接口的全部實現類。好比該方法能夠獲取 Protocol 接口的 DubboProtocol、HttpProtocol、InjvmProtocol 等實現類。在獲取實現類的過程當中,若是某個某個實現類被 Adaptive 註解修飾了,那麼該類就會被賦值給 cachedAdaptiveClass 變量。此時,上面步驟中的第二步條件成立(緩存不爲空),直接返回 cachedAdaptiveClass 便可

還有一個方法 createAdaptiveExtensionClass 這個方法很是特殊 , 是自動化生成擴展類的參數校驗類...
private Class<?> createAdaptiveExtensionClass() {
    //這裏爲何用代碼生成器來實現呢 type 是默認的接口 ,  經過一些邏輯動態的生成代碼 , 之後再看
    String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();
    ClassLoader classLoader = findClassLoader();
    org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
    return compiler.compile(code, classLoader);
}
注意這裏的一端代碼---自動生成Adaptive方法String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();,其中的generate這一塊應該是整個dubbo最難理解的地方了
public String generate() {
    // no need to generate adaptive class since there's no adaptive method found.
    //必須使用使用還有Adaptive註解包括的類才能實現
    if (!hasAdaptiveMethod()) {
        throw new IllegalStateException("No adaptive method exist on extension " + type.getName() + ", refuse to create the adaptive class!");
    }

    StringBuilder code = new StringBuilder();
    code.append(generatePackageInfo());
    code.append(generateImports());
    code.append(generateClassDeclaration());

    Method[] methods = type.getMethods();
    for (Method method : methods) {
        code.append(generateMethod(method));
    }
    code.append("}");

    if (logger.isDebugEnabled()) {
        logger.debug(code.toString());
    }
    return code.toString();
}

spi這裏其實寫的並非很合理,存在大量的問題,其實本質上dubbo在生成自適應類的時候,一個核心就是使用url做爲參數,各類數據其實都是在url中獲取的, 而後遍歷

看一個編譯以後的代碼

package org.apache.dubbo.rpc;

import org.apache.dubbo.common.extension.ExtensionLoader;


public class Protocol$Adaptive implements org.apache.dubbo.rpc.Protocol {
    public org.apache.dubbo.rpc.Exporter export(
        org.apache.dubbo.rpc.Invoker arg0)
        throws org.apache.dubbo.rpc.RpcException {
        if (arg0 == null) {
            throw new IllegalArgumentException(
                "org.apache.dubbo.rpc.Invoker argument == null");
        }

        if (arg0.getUrl() == null) {
            throw new IllegalArgumentException(
                "org.apache.dubbo.rpc.Invoker argument getUrl() == null");
        }

        org.apache.dubbo.common.URL url = arg0.getUrl();
        String extName = ((url.getProtocol() == null) ? "dubbo"
                                                      : url.getProtocol());

        if (extName == null) {
            throw new IllegalStateException(
                "Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (" +
                url.toString() + ") use keys([protocol])");
        }

        org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class)
                                                                                                 .getExtension(extName);

        return extension.export(arg0);
    }

    public java.util.List getServers() {
        throw new UnsupportedOperationException(
            "The method public default java.util.List org.apache.dubbo.rpc.Protocol.getServers() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
    }

    public org.apache.dubbo.rpc.Invoker refer(java.lang.Class arg0,
        org.apache.dubbo.common.URL arg1)
        throws org.apache.dubbo.rpc.RpcException {
        if (arg1 == null) {
            throw new IllegalArgumentException("url == null");
        }
        org.apache.dubbo.common.URL url = arg1;
        String extName = ((url.getProtocol() == null) ? "dubbo"
                                                      : url.getProtocol());

        if (extName == null) {
            throw new IllegalStateException(
                "Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (" +
                url.toString() + ") use keys([protocol])");
        }

        org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class)
                                                                                                 .getExtension(extName);

        return extension.refer(arg0, arg1);
    }
    public void destroy() {
        throw new UnsupportedOperationException(
            "The method public abstract void org.apache.dubbo.rpc.Protocol.destroy() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
    }
    public int getDefaultPort() {
        throw new UnsupportedOperationException(
            "The method public abstract int org.apache.dubbo.rpc.Protocol.getDefaultPort() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
    }
}

注意上面的邏輯重點實際上是 extName這個方法 , 這個方法使用自動生成邏輯中的generateExtNameAssignment生成的

private String generateExtNameAssignment(String[] value, boolean hasInvocation) {
    // TODO: refactor it
    String getNameCode = null;
    for (int i = value.length - 1; i >= 0; --i) {
        if (i == value.length - 1) {
            if (null != defaultExtName) {
                if (!"protocol".equals(value[i])) {
                    if (hasInvocation) {
                        getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName);
                    } else {
                        getNameCode = String.format("url.getParameter(\"%s\", \"%s\")", value[i], defaultExtName);
                    }
                } else {
                    getNameCode = String.format("( url.getProtocol() == null ? \"%s\" : url.getProtocol() )", defaultExtName);
                }
            } else {
                if (!"protocol".equals(value[i])) {
                    if (hasInvocation) {
                        getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName);
                    } else {
                        getNameCode = String.format("url.getParameter(\"%s\")", value[i]);
                    }
                } else {
                    getNameCode = "url.getProtocol()";
                }
            }
        } else {
            if (!"protocol".equals(value[i])) {
                if (hasInvocation) {
                    getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName);
                } else {
                    getNameCode = String.format("url.getParameter(\"%s\", %s)", value[i], getNameCode);
                }
            } else {
                getNameCode = String.format("url.getProtocol() == null ? (%s) : url.getProtocol()", getNameCode);
            }
        }
    }

    return String.format(CODE_EXT_NAME_ASSIGNMENT, getNameCode);
}

/**
    * @return
    */
private String generateExtensionAssignment() {
    return String.format(CODE_EXTENSION_ASSIGNMENT, type.getName(), ExtensionLoader.class.getSimpleName(), type.getName());
}

其中value是在Adaptive註解中標記的名稱,dubbo將會對這些名稱在url中進行自動化獲取,若是是protocal類型將會直接從url協議參數中獲取

ps 注意,dubbo在這裏作的其實並很差 , 邏輯很是的混亂 , 其中有一個很是重要的參數就是ExtName , 他的默認值是從AdaptiveClassCodeGenerator的構造函數中傳入的,而且
調用這個構造函數的ExtendLoad是在初始化ExtendLoad class 的時候,解析SPI註解中的參數進行默認注入的 。。。 其實注意,這裏本質上就是封裝一層從url中獲取參數而後給SPI實現類調用的邏輯

其餘重要方法injectExtension獲取這個類依賴的spi擴展屬性 , 使用set注入到這個類中

private T injectExtension(T instance) {

    if (objectFactory == null) {
        return instance;
    }

    try {
        for (Method method : instance.getClass().getMethods()) {
            if (!isSetter(method)) {
                continue;
            }
            /**
                * Check {@link DisableInject} to see if we need auto injection for this property
                */
            if (method.getAnnotation(DisableInject.class) != null) {
                continue;
            }
            Class<?> pt = method.getParameterTypes()[0];
            if (ReflectUtils.isPrimitives(pt)) {
                continue;
            }

            try {
                String property = getSetterProperty(method);
                Object object = objectFactory.getExtension(pt, property);
                if (object != null) {
                    method.invoke(instance, object);
                }
            } catch (Exception e) {
                logger.error("Failed to inject via method " + method.getName()
                        + " of interface " + type.getName() + ": " + e.getMessage(), e);
            }

        }
    } catch (Exception e) {
        logger.error(e.getMessage(), e);
    }
    return instance;
}
注意這個方法,dubbo的默認objectFactory使用的是ExtendsObjectFactory 因此,只能加載SPI的類型
相關文章
相關標籤/搜索