dubbo分析(1)-プラグイン化モジュール化能力類SPI能力設計解析


dubbo 2.7バージョンベース
dubooのプラグイン化はspiに基づいていると言われていますが、私は一つの観点を認めていません.dubboは自分がspiのようなspring bootを実現したと思います.
まとめ:dubboはjavaのspiを使用するのではなく、より強力なspiメカニズム(自動クラスロードメカニズム)を実現しました.
コアクラスExtensionLoader
dubbo自身がSPIメカニズムを実現
実はこの1枚のロジックはとても簡単で、核心のクラスExtensionLoaderの2つの方法の中に位置付けました
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 holder = cachedInstances.get(name);
    if (holder == null) {
        cachedInstances.putIfAbsent(name, new Holder());
        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;
}

2番目のコアメソッド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> 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全体の中で1つの硬いコアのクラスと言える、合計1000行以上のコード、名前を見るとこのクラスが実際にdubbo全体の動的ロードの論理を担っていることがわかります
Extensionのdubboでの使用方法は一般的にこのようなものである.
Xxx xxx = ExtensionLoader.getExtensionLoader(Xxx.class).getAdaptiveExtension();

この方法は,このクラスのカスタム拡張情報を取得し,パラメータ検証を行い,spi注釈でマークされたクラスでなければ使用できないことに注意する.
コアは実は2つの方法であり、getExtensionLoaderとgetAdaptiveExtensionであり、前者は1つのExtensionLoaderをインスタンス化し、後者は動的ロードを行う.
getExtensionLoader
この方法は簡単で,タイプとExtensionLoderオブジェクト間のマッピング関係を1つずつ対応させ,ExtensionLoaderオブジェクトのObjectFactoryタイプオブジェクトを初期化することである.
public static  ExtensionLoader getExtensionLoader(Class type) {
    //    
    ExtensionLoader loader = (ExtensionLoader) EXTENSION_LOADERS.get(type);
    if (loader == null) {
        //            
        EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader(type));
        loader = (ExtensionLoader) 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
このクラスは非常に複雑で、私は論理を簡略化して、キーの記録だけをします.
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;
}

重要な方法を見つけた
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);
    }
}

ちょっと吐き気がします.まずget AdaptiveExtensionClassの方法を見てみましょう.
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が現在のタイプをコンストラクション関数とするクラス)に注意します.
get AdaptiveExtensionClassメソッドには、次の3つのロジックが含まれています.
getExtensionClassesを呼び出してすべての拡張クラスチェックキャッシュを取得し、キャッシュが空でない場合はキャッシュを返します.キャッシュが空である場合は、createAdaptiveExtensionClassを呼び出して適応拡張クラスを作成する3つの論理は平凡に見えますが、あまり言う必要はありません.しかし、これらの平凡なコードにはいくつかの詳細が隠されており、説明する必要があります.まず、最初の論理から言えば、getExtensionClassesという方法は、あるインタフェースのすべての実装クラスを取得するために使用されます.例えば、この方法は、ProtocolインタフェースのDubboProtocol、HttpProtocol、InjvmProtocolなどの実装クラスを取得することができる.インプリメンテーションクラスを取得する過程で、あるインプリメンテーションクラスがAdaptive注釈で修飾された場合、そのクラスはcachedAdaptiveClass変数に割り当てられます.この場合、上記ステップの2ステップ目の条件が成立し(キャッシュが空ではない)、そのままcachedAdaptiveClassに戻ればよい
もう1つの方法は、拡張クラスの生成を自動化するパラメータチェッククラスです.
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は適応クラスを生成する際に、1つの核心は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という方法であることに注意してください.この方法は自動生成論理のgenerateExtName Assignmentを使用して生成されます.
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のタイプしかロードできません