Dubbo(一)-SPI(2) 機制之 Dubbo 的 SPI

fightcrap發表於2019-03-08

Dubbo(一)-SPI(2) 機制之 Dubbo 的 SPI

Dubbo 的 SPI 是什麼

下文描述來自官網介紹:

Dubbo 的擴充套件點載入從 JDK 標準的 SPI (Service Provider Interface) 擴充套件點發現機制加強而來。

Dubbo 改進了 JDK 標準的 SPI 的以下問題:

  • JDK 標準的 SPI 會一次性例項化擴充套件點所有實現,如果有擴充套件實現初始化很耗時,但如果沒用上也載入,會很浪費資源。
  • 如果擴充套件點載入失敗,連擴充套件點的名稱都拿不到了。比如:JDK 標準的 ScriptEngine,通過 getName() 獲取指令碼型別的名稱,但如果 RubyScriptEngine 因為所依賴的 jruby.jar 不存在,導致 RubyScriptEngine 類載入失敗,這個失敗原因被吃掉了,和 ruby 對應不起來,當使用者執行 ruby 指令碼時,會報不支援 ruby,而不是真正失敗的原因。
  • 增加了對擴充套件點 IoC 和 AOP 的支援,一個擴充套件點可以直接 setter 注入其它擴充套件點。

Dubbo 的 SPI 核心檔案目錄

dubbo-spi檔案目錄
沒錯就只有那麼一點,是不是覺得很神奇。具體介紹內容前,先介紹下檔案作用

註解部分

  1. @SPI

    該註解是標記介面是擴充套件介面,也就是說想要使用 Dubbo 的 SPI 就必須打上這個註解。

    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.TYPE})
    public @interface SPI {
    
    /**
     * default extension name
     */
    String value() default "";
    
    }
    複製程式碼

    value 的作用是標記 spi 擴充套件所需要的對應類的。和上篇 javaSpi 作用其實是一樣的,只不過 dubbo 弄了個標示可以選擇不同的值,以 xxx=xxxx 的形式選擇

    e.g:

    dubbo-spi檔案目錄

  2. @Adaptive

    該註解是整個功能的核心,它的作用有兩個:

    • 作用在類上面,這代表該實現類是唯一承認的介面實現類,也就是哪怕我在@SPI 註解上指定了對應的實現類,也是不認噠
    • 作用在方法上(這個需要和@SPI 註解連用),而且引數中必須有一個 org.apache.dubbo.common.URL 才行,Dubbo 會對應生成一個 xxx$Adaptive 類,作為一箇中間類,用於選擇提供不同的實現類。
    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.TYPE, ElementType.METHOD})
    public @interface Adaptive {
    
        String[] value() default {};
    
    }
    複製程式碼
  3. @Activate

    該註解是擴充套件點自動啟用載入的註解,就是用條件來控制該擴充套件點實現是否被自動啟用載入,在擴充套件實現類上面使用。

介面實現部分

  1. ExtensionFactory

    SPI 利用工廠模式來實現各功能,其中前言也講到過,Dubbo 的 SPI 增加了對擴充套件點 IoC 和 AOP 的支援,這個工廠也是實現這個的基礎,它有 3 個實現類:AdaptiveExtensionFactory,SpiExtensionFactory 和 SpringExtensionFactory。同樣該介面也是被@SPI 修飾的,所以它也是可以擴充套件的,但是 AdaptiveExtensionFactory 有@Adaptive 實現,所以它是唯一作用的。它把所有 ExtensionFactory 的實現獲取了,通過遍歷的形式,執行兩種不同的獲取方式,相容了 Spring 框架的形式 ExtensionFactory:

    @SPI
    public interface ExtensionFactory {
    
        /**
        * Get extension.
        *
        * @param type object type.
        * @param name object name.
        * @return object instance.
        */
        <T> T getExtension(Class<T> type, String name);
    
    }
    複製程式碼

    AdaptiveExtensionFactory:

    public AdaptiveExtensionFactory() {
        ExtensionLoader<ExtensionFactory> loader = ExtensionLoader.getExtensionLoader(ExtensionFactory.class);
        List<ExtensionFactory> list = new ArrayList<ExtensionFactory>();
        for (String name : loader.getSupportedExtensions()) {
            list.add(loader.getExtension(name));
        }
        factories = Collections.unmodifiableList(list);
    }
    
    @Override
    public <T> T getExtension(Class<T> type, String name) {
        for (ExtensionFactory factory : factories) {
            T extension = factory.getExtension(type, name);
            if (extension != null) {
                return extension;
            }
        }
        return null;
    }
    複製程式碼

    SpiExtensionFactory:

    public class SpiExtensionFactory implements ExtensionFactory {
    
    @Override
    public <T> T getExtension(Class<T> type, String name) {
        if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {
            ExtensionLoader<T> loader = ExtensionLoader.getExtensionLoader(type);
            if (!loader.getSupportedExtensions().isEmpty()) {
                return loader.getAdaptiveExtension();
            }
        }
        return null;
    }
    
    }
    複製程式碼

    SpringExtensionFactory:

    public <T> T getExtension(Class<T> type, String name) {
    
        //SPI should be get from SpiExtensionFactory
        if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {
            return null;
        }
    
        for (ApplicationContext context : contexts) {
            if (context.containsBean(name)) {
                Object bean = context.getBean(name);
                if (type.isInstance(bean)) {
                    return (T) bean;
                }
            }
        }
    
        logger.warn("No spring extension (bean) named:" + name + ", try to find an extension (bean) of type " + type.getName());
    
        if (Object.class == type) {
            return null;
        }
    
        for (ApplicationContext context : contexts) {
            try {
                return context.getBean(type);
            } catch (NoUniqueBeanDefinitionException multiBeanExe) {
                logger.warn("Find more than 1 spring extensions (beans) of type " + type.getName() + ", will stop auto injection. Please make sure you have specified the concrete parameter type and there's only one extension of that type.");
            } catch (NoSuchBeanDefinitionException noBeanExe) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Error when get spring extension(bean) for type:" + type.getName(), noBeanExe);
                }
            }
        }
    
        logger.warn("No spring extension (bean) named:" + name + ", type:" + type.getName() + " found, stop get bean.");
    
        return null;
    }
    複製程式碼

    ok 全部 dubbo 的 spi 檔案內容就是如此,我們在看一下執行的細則

Dubbo 的 SPI 執行流程

  1. 執行入口:
 ExtensionLoader<SayWord> loader = ExtensionLoader.getExtensionLoader(SayWord.class);
複製程式碼

呼叫 ExtensionLoader 獲取 ExtensionLoader 物件。這個方法主要是獲取對應 SPI 介面的 ExtemsionLoader。

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);
        if (loader == null) {
            EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
            loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
        }
        return loader;
    }
複製程式碼

根據原始碼我們可以看到,首先會進行條件判斷,如果 Class 物件不是介面,而且沒有 SPI 註解,則返回異常。如果符合要求,就會在 EXTENSION_LOADERS 物件中獲取,如果已經快取了,則返回快取的物件,如果沒有快取,則新建 new 一個 ExtensionLoader 物件。EXTENSION_LOADERS 是一個 ConcurrentMap<Class, ExtensionLoader>。是一層快取。

來看一下建立一個 ExtensionLoader 物件做了什麼。這是一個私有建構函式哦,只允許內部建立。

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

這裡無非就是設定了 type,objectFactory 物件。一般情況下我們設定 spi 物件都不會是 ExtensionFactory,所以大部分都會走後面邏輯,會優先建立 ExtensionFactory 的 ExtensionLoader 物件。

getAdaptiveExtension()做了啥?

這個方法其實就是獲取了 spi 對應實現形式,類似的還有 getActivateExtension()。其實這個很好理解,分別對應了我們兩個註解的功能:@Adaptive 和@Activate。Spi 核心還是 getAdaptiveExtension()方法。

public T getAdaptiveExtension() {
        Object instance = cachedAdaptiveInstance.get();
        if (instance == null) {
            if(createAdaptiveInstanceError == null) {
                synchronized (cachedAdaptiveInstance) {
                    instance = cachedAdaptiveInstance.get();
                    if (instance == null) {
                        try {
                            instance = createAdaptiveExtension();
                            cachedAdaptiveInstance.set(instance);
                        } catch (Throwable t) {
                            createAdaptiveInstanceError = t;
                            throw new IllegalStateException("fail to create adaptive instance: " + t.toString(), t);
                        }
                    }
                }
            }
            else {
                throw new IllegalStateException("fail to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError);
            }
        }

        return (T) instance;
    }
複製程式碼

讓我們來看看具體是啥樣子的:優先判斷快取的 hoder 是否存在,在沒有呼叫過這個方法之前,該物件只是創立了個空物件:

private final Holder<Object> cachedAdaptiveInstance = new Holder<Object>();
複製程式碼

所以第一次獲取的是空。那麼會進入邏輯判斷內部,然後判斷是否存在異常,會把第一次建立的異常會快取進。所以這邊會快取 Adaptive 實現物件或者就是異常物件。 再繼續看,利用了同步鎖二重判斷,來確保併發的安全性,然後呼叫 createAdaptiveExtension()方法來建立。然後快取了結果。下面是 createAdaptiveExtension()實現

 private T createAdaptiveExtension() {
        try {
            return injectExtension((T) getAdaptiveExtensionClass().newInstance());
        } catch (Exception e) {
            throw new IllegalStateException("Can not create adaptive extenstion " + type + ", cause: " + e.getMessage(), e);
        }
    }
複製程式碼

會優先呼叫 getAdaptiveExtensionClass()方法然後例項化。其中流程很簡單,最核心的部分為:getAdaptiveExtensionClass()->getExtensionClasses()->loadExtensionClasses(); loadExtensionClasses()主要是載入spi中的類檔案,這部分就和正常是spi一樣了

private Map<String, Class<?>> loadExtensionClasses() {
        final SPI defaultAnnotation = type.getAnnotation(SPI.class);
        if(defaultAnnotation != null) {
            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<?>>();
        loadFile(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
        loadFile(extensionClasses, DUBBO_DIRECTORY);
        loadFile(extensionClasses, SERVICES_DIRECTORY);
        return extensionClasses;
    }
複製程式碼

而在getExtensionClasses()方法中:

private Class<?> getAdaptiveExtensionClass() {
        getExtensionClasses();
        if (cachedAdaptiveClass != null) {
            return cachedAdaptiveClass;
        }
        return cachedAdaptiveClass = createAdaptiveExtensionClass();
    }
複製程式碼

如果存在cachedAdaptiveClass就會返回這個,所以這個也就是未什麼@Adaptive註解就是唯一標示了(其中cachedAdaptiveClass的產生在loadFile方法裡。可以看看)

如果沒有cachedAdaptiveClass就會建立一個,

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);
    }
複製程式碼

這邊核心的部分就是 createAdaptiveExtensionClassCode()了,來看看裡面實現啥

private String createAdaptiveExtensionClassCode() {
        StringBuilder codeBuidler = new StringBuilder();
        Method[] methods = type.getMethods();
        boolean hasAdaptiveAnnotation = false;
        for(Method m : methods) {
            if(m.isAnnotationPresent(Adaptive.class)) {
                hasAdaptiveAnnotation = true;
                break;
            }
        }
        // 完全沒有Adaptive方法,則不需要生成Adaptive類
        if(! hasAdaptiveAnnotation)
            throw new IllegalStateException("No adaptive method on extension " + type.getName() + ", refuse to create the adaptive class!");
        
        codeBuidler.append("package " + type.getPackage().getName() + ";");
        codeBuidler.append("\nimport " + ExtensionLoader.class.getName() + ";");
        codeBuidler.append("\npublic class " + type.getSimpleName() + "$Adpative" + " implements " + type.getCanonicalName() + " {");
        
        for (Method method : methods) {
            Class<?> rt = method.getReturnType();
            Class<?>[] pts = method.getParameterTypes();
            Class<?>[] ets = method.getExceptionTypes();

            Adaptive adaptiveAnnotation = method.getAnnotation(Adaptive.class);
            StringBuilder code = new StringBuilder(512);
            if (adaptiveAnnotation == null) {
                code.append("throw new UnsupportedOperationException(\"method ")
                        .append(method.toString()).append(" of interface ")
                        .append(type.getName()).append(" is not adaptive method!\");");
            } else {
                int urlTypeIndex = -1;
                for (int i = 0; i < pts.length; ++i) {
                    if (pts[i].equals(URL.class)) {
                        urlTypeIndex = i;
                        break;
                    }
                }
                // 有型別為URL的引數
                if (urlTypeIndex != -1) {
                    // Null Point check
                    String s = String.format("\nif (arg%d == null) throw new IllegalArgumentException(\"url == null\");",
                                    urlTypeIndex);
                    code.append(s);
                    
                    s = String.format("\n%s url = arg%d;", URL.class.getName(), urlTypeIndex); 
                    code.append(s);
                }
                // 引數沒有URL型別
                else {
                    String attribMethod = null;
                    
                    // 找到引數的URL屬性
                    LBL_PTS:
                    for (int i = 0; i < pts.length; ++i) {
                        Method[] ms = pts[i].getMethods();
                        for (Method m : ms) {
                            String name = m.getName();
                            if ((name.startsWith("get") || name.length() > 3)
                                    && Modifier.isPublic(m.getModifiers())
                                    && !Modifier.isStatic(m.getModifiers())
                                    && m.getParameterTypes().length == 0
                                    && m.getReturnType() == URL.class) {
                                urlTypeIndex = i;
                                attribMethod = name;
                                break LBL_PTS;
                            }
                        }
                    }
                    if(attribMethod == null) {
                        throw new IllegalStateException("fail to create adative class for interface " + type.getName()
                        		+ ": not found url parameter or url attribute in parameters of method " + method.getName());
                    }
                    
                    // Null point check
                    String s = String.format("\nif (arg%d == null) throw new IllegalArgumentException(\"%s argument == null\");",
                                    urlTypeIndex, pts[urlTypeIndex].getName());
                    code.append(s);
                    s = String.format("\nif (arg%d.%s() == null) throw new IllegalArgumentException(\"%s argument %s() == null\");",
                                    urlTypeIndex, attribMethod, pts[urlTypeIndex].getName(), attribMethod);
                    code.append(s);

                    s = String.format("%s url = arg%d.%s();",URL.class.getName(), urlTypeIndex, attribMethod); 
                    code.append(s);
                }
                
                String[] value = adaptiveAnnotation.value();
                // 沒有設定Key,則使用“擴充套件點介面名的點分隔 作為Key
                if(value.length == 0) {
                    char[] charArray = type.getSimpleName().toCharArray();
                    StringBuilder sb = new StringBuilder(128);
                    for (int i = 0; i < charArray.length; i++) {
                        if(Character.isUpperCase(charArray[i])) {
                            if(i != 0) {
                                sb.append(".");
                            }
                            sb.append(Character.toLowerCase(charArray[i]));
                        }
                        else {
                            sb.append(charArray[i]);
                        }
                    }
                    value = new String[] {sb.toString()};
                }
                
                boolean hasInvocation = false;
                for (int i = 0; i < pts.length; ++i) {
                    if (pts[i].getName().equals("com.alibaba.dubbo.rpc.Invocation")) {
                        // Null Point check
                        String s = String.format("\nif (arg%d == null) throw new IllegalArgumentException(\"invocation == null\");", i);
                        code.append(s);
                        s = String.format("\nString methodName = arg%d.getMethodName();", i); 
                        code.append(s);
                        hasInvocation = true;
                        break;
                    }
                }
                
                String defaultExtName = cachedDefaultName;
                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);
                    }
                }
                code.append("\nString extName = ").append(getNameCode).append(";");
                // check extName == null?
                String s = String.format("\nif(extName == null) " +
                		"throw new IllegalStateException(\"Fail to get extension(%s) name from url(\" + url.toString() + \") use keys(%s)\");",
                        type.getName(), Arrays.toString(value));
                code.append(s);
                
                s = String.format("\n%s extension = (%<s)%s.getExtensionLoader(%s.class).getExtension(extName);",
                        type.getName(), ExtensionLoader.class.getSimpleName(), type.getName());
                code.append(s);
                
                // return statement
                if (!rt.equals(void.class)) {
                    code.append("\nreturn ");
                }

                s = String.format("extension.%s(", method.getName());
                code.append(s);
                for (int i = 0; i < pts.length; i++) {
                    if (i != 0)
                        code.append(", ");
                    code.append("arg").append(i);
                }
                code.append(");");
            }
            
            codeBuidler.append("\npublic " + rt.getCanonicalName() + " " + method.getName() + "(");
            for (int i = 0; i < pts.length; i ++) {
                if (i > 0) {
                    codeBuidler.append(", ");
                }
                codeBuidler.append(pts[i].getCanonicalName());
                codeBuidler.append(" ");
                codeBuidler.append("arg" + i);
            }
            codeBuidler.append(")");
            if (ets.length > 0) {
                codeBuidler.append(" throws ");
                for (int i = 0; i < ets.length; i ++) {
                    if (i > 0) {
                        codeBuidler.append(", ");
                    }
                    codeBuidler.append(pts[i].getCanonicalName());
                }
            }
            codeBuidler.append(" {");
            codeBuidler.append(code.toString());
            codeBuidler.append("\n}");
        }
        codeBuidler.append("\n}");
        if (logger.isDebugEnabled()) {
            logger.debug(codeBuidler.toString());
        }
        return codeBuidler.toString();
    }
複製程式碼

那麼大一串其實就是生成了一個class的程式碼。這邊看一下生成的樣子是啥樣子的

package com.pangxie.server.dubbo.dubbo.spi;

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

public class SayWord$Adpative implements com.pangxie.server.dubbo.dubbo.spi.api.SayWord {
    @Override
    public java.lang.String saySomething(java.lang.String arg0, com.alibaba.dubbo.common.URL arg1) {
        if (arg1 == null) throw new IllegalArgumentException("url == null");
        com.alibaba.dubbo.common.URL url = arg1;
        String extName = url.getParameter("say.word", "en");
        if (extName == null)
            throw new IllegalStateException("Fail to get extension(com.pangxie.server.dubbo.dubbo.spi.api.SayWord) name from url(" + url.toString() + ") use keys([say.word])");
        com.pangxie.server.dubbo.dubbo.spi.api.SayWord extension = (com.pangxie.server.dubbo.dubbo.spi.api.SayWord) ExtensionLoader.getExtensionLoader(com.pangxie.server.dubbo.dubbo.spi.api.SayWord.class).getExtension(extName);
        return extension.saySomething(arg0, arg1);
    }
}
複製程式碼

其中,標示了@SPI中的value值就會變成這邊的預設值。也就是如果沒有設計URL內容,那麼返回的都是預設spi的介面。 所以總共的流程就是如此了。接下來看看不同情況的執行結果。在來理解下生成的class程式碼的執行。

Dubbo 的 xxx$Adpative 不同執行

測試程式碼如下

介面

@SPI
public interface SayWord {

    @Adaptive
    String saySomething(String message,URL url);
}
複製程式碼

實現1

public class SayChineseWord implements SayWord {
    @Override
    public String saySomething(String message, URL url) {
        return "你好啊";
    }
}
複製程式碼

實現2

public class SayEnglishWord implements SayWord {
    @Override
    public String saySomething(String message, URL url) {
        return "Hello";
    }
}

複製程式碼

配置檔案:

cn=com.pangxie.server.dubbo.dubbo.spi.impl.SayChineseWord
en=com.pangxie.server.dubbo.dubbo.spi.impl.SayEnglishWord
複製程式碼

執行入口

public class Main {

    public static void main(String[] args) {
        ExtensionLoader<SayWord> loader = ExtensionLoader.getExtensionLoader(SayWord.class);
        SayWord sayWord=loader.getAdaptiveExtension();
        URL url = URL.valueOf("test://localhost/test");
        System.out.println(sayWord.saySomething("d", url));
    }
}

複製程式碼
  1. @Adaptive沒有預設值,@SPI沒有預設值,URL沒有值
package com.pangxie.server.dubbo.dubbo.spi.api;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
public class SayWord$Adpative implements com.pangxie.server.dubbo.dubbo.spi.api.SayWord {
public java.lang.String saySomething(java.lang.String arg0, com.alibaba.dubbo.common.URL arg1) {
if (arg1 == null) throw new IllegalArgumentException("url == null");
com.alibaba.dubbo.common.URL url = arg1;
String extName = url.getParameter("say.word");
if(extName == null) throw new IllegalStateException("Fail to get extension(com.pangxie.server.dubbo.dubbo.spi.api.SayWord) name from url(" + url.toString() + ") use keys([say.word])");
com.pangxie.server.dubbo.dubbo.spi.api.SayWord extension = (com.pangxie.server.dubbo.dubbo.spi.api.SayWord)ExtensionLoader.getExtensionLoader(com.pangxie.server.dubbo.dubbo.spi.api.SayWord.class).getExtension(extName);
return extension.saySomething(arg0, arg1);
}
}
複製程式碼

生成的程式碼為上訴所示;其實結果我們已經可以猜想到,spi沒有預設值,url中也沒有標示。所以結果肯定是啥都找不到的

Exception in thread "main" java.lang.IllegalStateException: Fail to get extension(com.pangxie.server.dubbo.dubbo.spi.api.SayWord) name from url(test://localhost/test) use keys([say.word])
	at com.pangxie.server.dubbo.dubbo.spi.api.SayWord$Adpative.saySomething(SayWord$Adpative.java)
	at com.pangxie.server.dubbo.dubbo.spi.Main.main(Main.java:33)
複製程式碼
  1. @Adaptive沒有預設值,@SPI沒有預設值,URL有值
package com.pangxie.server.dubbo.dubbo.spi.api;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
public class SayWord$Adpative implements com.pangxie.server.dubbo.dubbo.spi.api.SayWord {
public java.lang.String saySomething(java.lang.String arg0, com.alibaba.dubbo.common.URL arg1) {
if (arg1 == null) throw new IllegalArgumentException("url == null");
com.alibaba.dubbo.common.URL url = arg1;
String extName = url.getParameter("say.word");
if(extName == null) throw new IllegalStateException("Fail to get extension(com.pangxie.server.dubbo.dubbo.spi.api.SayWord) name from url(" + url.toString() + ") use keys([say.word])");
com.pangxie.server.dubbo.dubbo.spi.api.SayWord extension = (com.pangxie.server.dubbo.dubbo.spi.api.SayWord)ExtensionLoader.getExtensionLoader(com.pangxie.server.dubbo.dubbo.spi.api.SayWord.class).getExtension(extName);
return extension.saySomething(arg0, arg1);
}
}
複製程式碼

生成程式碼如上訴所示。猜猜看結果,

你好啊
Disconnected from the target VM, address: '127.0.0.1:64482', transport: 'socket'

Process finished with exit code 0
複製程式碼

輸出了我們以cn為主的實現類的結果。所以如果沒有設定預設值,我們可以利用URL設定的方式,來進行選擇,而且這邊也有一個好處,就是可以動態選擇。只需要通過設定URL的值,就可以避免編碼修改的方式,比原來的靈活十足。

  1. @Adaptive沒有預設值,@SPI有預設值,URL有值 設定SPI預設值為en,URL為cn
package com.pangxie.server.dubbo.dubbo.spi.api;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
public class SayWord$Adpative implements com.pangxie.server.dubbo.dubbo.spi.api.SayWord {
public java.lang.String saySomething(java.lang.String arg0, com.alibaba.dubbo.common.URL arg1) {
if (arg1 == null) throw new IllegalArgumentException("url == null");
com.alibaba.dubbo.common.URL url = arg1;
String extName = url.getParameter("say.word", "en");
if(extName == null) throw new IllegalStateException("Fail to get extension(com.pangxie.server.dubbo.dubbo.spi.api.SayWord) name from url(" + url.toString() + ") use keys([say.word])");
com.pangxie.server.dubbo.dubbo.spi.api.SayWord extension = (com.pangxie.server.dubbo.dubbo.spi.api.SayWord)ExtensionLoader.getExtensionLoader(com.pangxie.server.dubbo.dubbo.spi.api.SayWord.class).getExtension(extName);
return extension.saySomething(arg0, arg1);
}
}
複製程式碼

生成原始碼如上述所示

你好啊
Disconnected from the target VM, address: '127.0.0.1:64614', transport: 'socket'

Process finished with exit code 0

複製程式碼

最終結果以url的key值為主哦。

總結

該文章講解了dubbo的SPI擴充套件機制的實現原理,最關鍵的是弄清楚dubbo跟jdk在實現SPI的思想上做了哪些改進和優化,解讀dubbo SPI擴充套件機制最關鍵的是弄清楚@SPI、@Adaptive、@Activate三個註解的含義,大部分邏輯都被封裝在ExtensionLoader類中。dubbo的spi學習下來讓我感覺一點就是靈活性是在是太突出了,不僅僅是根據註解,也可以根據引數形式,利用建立中間包裝類,使用裝飾模式,來加強spi的靈活。根據上一節其實我們可以知道dubbo選擇spi內容的順序為 @Adaptive》URL》SPI預設值。

相關文章