Dubbo2.7源碼分析-SPI的應(yīng)用

SPI簡介

SPI是Service Provider Interface的縮寫撑瞧,即服務(wù)提供接口(翻譯出來好繞口,還是不翻譯的好)镜雨,實質(zhì)上是接口角虫,作用是對外提供服務(wù)苦蒿。
SPI是Java的一種插件機制桐猬,可以不用修改源代碼實現(xiàn)新功能的擴展。
主要有如下幾個步驟:

  1. 實現(xiàn)SPI接口
  2. 在項目的META-INF/services文件夾下刽肠,新建一個以SPI接口命名的文件溃肪, 文件里面配置上SPI接口的實現(xiàn)類
  3. 使用java.util.ServiceLoader加載。
    由于本篇文章主要講解Dubbo是如何使用SPI的音五,如果想要具體了解Java的SPI惫撰,可以參考下面兩篇文章:

Dubbo SPI

回到正題厨钻,SPI在dubbo應(yīng)用的地方很多,專業(yè)一點講叫做微內(nèi)核機制;
如下圖:

Plug-In

我們拿其中一個標(biāo)簽進行講解坚嗜,我們在使用dubbo框架時夯膀,會配置<dubbo:protocol />標(biāo)簽,告訴dubbo服務(wù)的主機苍蔬、端口诱建、可接收的最大連接數(shù)、使用哪個協(xié)議碟绑,協(xié)議的傳輸控制器(netty,servlet,jetty等)俺猿、線程池類型大小等信息。dubbo協(xié)議默認使用的是netty網(wǎng)絡(luò)傳輸框架格仲,當(dāng)然還可以使用mina押袍、grizzly,只需要配置transporter凯肋、server谊惭、client為相應(yīng)的值即可。那dubbo是如何根據(jù)不同的配置使用不同的網(wǎng)絡(luò)傳輸框架的呢,當(dāng)然是通過SPI啦圈盔。java spi有一個配置文件惭蟋,那dubbo是否也有呢?在dubbo-rpc包下的dubbo-rpc-dubbo子包下药磺,發(fā)現(xiàn)了一個配置文件


image.png

我們來看下配置文件的內(nèi)容:

dubbo=org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol

配置了一個鍵值對,key為dubbo,值為org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol煤伟,在其它幾個子包下癌佩,也有名稱叫做org.apache.dubbo.rpc.Protocol的配置文件,說明Protocol插口有幾個對應(yīng)的插件

可以猜測一下便锨,當(dāng)<dubbo:protocol />僅僅配置了name="dubbo"围辙,port="20880"時,會加載哪一個協(xié)議插件呢放案,根據(jù)名稱姚建,可以猜測,加載的DubboProtocol插件吱殉。那dubbo是怎樣做到的呢掸冤,我們來一探究竟。

Dubbo為使用SPI做的準備工作:

三個注解

  • SPI:這個注解使用在接口上友雳,標(biāo)識接口是否是extension(擴展或插口)稿湿,可以接收一個默認的extension名稱
  • Adaptive: 這個注解可以使用在類或方法上,決定加載哪一個extension,值為字符串?dāng)?shù)組押赊,數(shù)組中的字符串是key值饺藤,比如new String[]{"key1","key2"};先在URL中尋找key1的值,如果找到流礁,則使用此值加載extension涕俗,如果key1沒有,則尋找key2的值神帅,如果key2也沒有再姑,則使用接口SPI注解的值,如果接口SPI注解找御,沒有配置默認值询刹,則將接口名按照首字母大寫分成多個部分,然后以'.'分隔萎坷,例如org.apache.dubbo.xxx.YyyInvokerWrapper接口名會變成yyy.invoker.wrapper凹联,然后以此名稱做為key到URL尋找,如果仍沒有找到哆档,則拋出IllegalStateException異常蔽挠;Adaptive注解用在類上,表示此類是它實現(xiàn)接口(插口)的自適應(yīng)插件
  • Activate:這個注解可以使用在類或方法上,用以根據(jù)URL的key值判斷當(dāng)前extension是否生效澳淑,當(dāng)一個extension有多個實現(xiàn)時比原,可以加載特定的extension實現(xiàn)類,例如extension實現(xiàn)類上有注解@Activate("cache, validation")杠巡,則當(dāng)URL上出現(xiàn)"cache”或“validation" key時量窘,當(dāng)前extension才會生效

ExtensionLoader

顧名思義,ExtensionLoader用于加載extension氢拥,它的作用有三點:1.自動加載extension;2.自動包裝(wrap) extension;3.創(chuàng)建自適應(yīng)的(adaptive)extension;

旅途開始

先看下上篇文章中Provider端的配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">
 
    <!-- 提供方應(yīng)用信息蚌铜,用于計算依賴關(guān)系 -->
    <dubbo:application name="hello-world-app"  />
 
    <!-- 使用multicast廣播注冊中心暴露服務(wù)地址 -->
    <dubbo:registry address="multicast://224.5.6.7:1234" />
 
    <!-- 用dubbo協(xié)議在20880端口暴露服務(wù) -->
    <dubbo:protocol name="dubbo" port="20880" />
 
    <!-- 聲明需要暴露的服務(wù)接口 -->
    <dubbo:service interface="com.alibaba.dubbo.demo.DemoService" ref="demoService" />
 
    <!-- 和本地bean一樣實現(xiàn)服務(wù) -->
    <bean id="demoService" class="com.alibaba.dubbo.demo.provider.DemoServiceImpl" />
</beans>

還是先從ClassPathXmlApplicationContext加載spring配置文件說起,上回我們說到ClassPathXmlApplicationContext會使用XmlBeanDefinitionReader將xml文件解析成BeanDefiniton集合嫩海,當(dāng)解析<dubbo:protocol />標(biāo)簽時冬殃,會將其解析成org.apache.dubbo.config.ProtocolConfig對象(為什么?請看上回分解最后,protocol key 實例化DubboBeanDefinitionParser時傳入的參數(shù)),解析<dubbo:service />時叁怪,會將其解析成org.apache.dubbo.config.spring.ServiceBean對象审葬。在解析xml時,會調(diào)用AbstractApplicationContext的refresh()方法

ServiceBean是ServiceConfig的子類奕谭,所以在創(chuàng)建ServiceBean對象的時候涣觉,會去先實例化父類,ServiceConfig中有一個static final成員變量protocol

private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();

ExtensionLoader終于出場了血柳,想要獲取插件旨枯,得分兩步走,第一步得到Protocol的插件加載對象extensionLoader混驰,然后由這個加載對象獲得對應(yīng)的插件攀隔。
先來看第一步:

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

EXTENSION_LOADERS保存的是目前已經(jīng)保存的插口的加載類,顯然第一次加載的時候栖榨,Protocol還沒有自己的插件加載類昆汹,那么需要實例化一個。實例化加載對象之后婴栽,用這個對象去加載插件满粗。

    public T getAdaptiveExtension() {
       //從已經(jīng)緩存的自適應(yīng)對象中獲得,第一次調(diào)用時還沒有創(chuàng)建自適應(yīng)類愚争,所以instance為null
        Object instance = cachedAdaptiveInstance.get();
        if (instance == null) {
            if (createAdaptiveInstanceError == null) {
                synchronized (cachedAdaptiveInstance) {
                    instance = cachedAdaptiveInstance.get();
                    if (instance == null) {
                        try {
                            //創(chuàng)建一個自適應(yīng)類
                            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;
    }

主要關(guān)注 instance = createAdaptiveExtension();這句映皆,createAdaptiveExtension()方法是什么樣的呢?

    private T createAdaptiveExtension() {
        try {
           //得到自適應(yīng)類并實現(xiàn)化,然后注入屬性值
            return injectExtension((T) getAdaptiveExtensionClass().newInstance());
        } catch (Exception e) {
            throw new IllegalStateException("Can not create adaptive extension " + type + ", cause: " + e.getMessage(), e);
        }
    }

getAdaptiveExtensionClass():

    private Class<?> getAdaptiveExtensionClass() {
       //1.獲取所有實現(xiàn)Protocol插口的插件類
        getExtensionClasses();
       //2.如果有自適應(yīng)插件類轰枝,則返回
        if (cachedAdaptiveClass != null) {
            return cachedAdaptiveClass;
        }
       //3.如果沒有捅彻,則創(chuàng)建插件類
        return cachedAdaptiveClass = createAdaptiveExtensionClass();
    }

先來看上面的第1步,getExtensionClasses()

    private Map<String, Class<?>> getExtensionClasses() {
       //從緩存中獲取插件類鞍陨,第一次肯定沒有
        Map<String, Class<?>> classes = cachedClasses.get();
        if (classes == null) {
            synchronized (cachedClasses) {
                classes = cachedClasses.get();
                if (classes == null) {
                    //實際的加載插件類方法
                    classes = loadExtensionClasses();
                    cachedClasses.set(classes);
                }
            }
        }
        return classes;
    }

   //ExtensionLoader中的三個常量步淹,加載插件的目錄,第一個熟悉吧,是java spi的默認目錄
    private static final String SERVICES_DIRECTORY = "META-INF/services/";
    private static final String DUBBO_DIRECTORY = "META-INF/dubbo/";
    private static final String DUBBO_INTERNAL_DIRECTORY = DUBBO_DIRECTORY + "internal/";

    private Map<String, Class<?>> loadExtensionClasses() {
        //獲取插口上SPI注解的值,默認值只能有一個,如果多于一個缭裆,則拋異常
        final SPI defaultAnnotation = type.getAnnotation(SPI.class);
        if (defaultAnnotation != null) {
            String value = defaultAnnotation.value();
            if ((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];
            }
        }

       //加載以上三個目錄下的實現(xiàn)了相應(yīng)插口的插件類(本例中插口是Protocol)
        Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
        loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName());
        loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
        loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName());
        loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
        loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName());
        loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
        return extensionClasses;
    }

實現(xiàn)Protocol插口的共有四個插件:


protocol插件

再來看上面getAdaptiveExtensionClass方法的第2步键闺,這一句是判斷有沒有自適應(yīng)類,在加載配置的插件過程中澈驼,會判斷此插件類是不是自適應(yīng)插件類辛燥,判斷的依據(jù)就是插件類上是否有注解@Adaptive,Protocol的這四個插件類上都沒有此注解缝其,所以沒有自適應(yīng)插件挎塌,則會走到第3步,創(chuàng)建一個自適應(yīng)插件類

   private Class<?> createAdaptiveExtensionClass() {
        //生成類代碼
        String code = createAdaptiveExtensionClassCode();
        ClassLoader classLoader = findClassLoader();
        //得到編輯器氏淑,并將類代碼編譯成字節(jié)碼
        org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
        return compiler.compile(code, classLoader);
    }

    //來看看生成類代碼的過程,以生成Protocol插件類代碼為例
    private String createAdaptiveExtensionClassCode() {
        StringBuilder codeBuilder = new StringBuilder();
       //得到Protocol接口所有方法
        Method[] methods = type.getMethods();
        boolean hasAdaptiveAnnotation = false;
        for (Method m : methods) {
            if (m.isAnnotationPresent(Adaptive.class)) {
                hasAdaptiveAnnotation = true;
                break;
            }
        }
        // // 如果方法上沒有@Adaptive注解,則不能創(chuàng)建自適應(yīng)插件類
        if (!hasAdaptiveAnnotation)
            throw new IllegalStateException("No adaptive method on extension " + type.getName() + ", refuse to create the adaptive class!");

        codeBuilder.append("package ").append(type.getPackage().getName()).append(";");
        codeBuilder.append("\nimport ").append(ExtensionLoader.class.getName()).append(";");
       //類名為Protocol$Adaptive實現(xiàn)了Protocol接口
        codeBuilder.append("\npublic class ").append(type.getSimpleName()).append("$Adaptive").append(" implements ").append(type.getCanonicalName()).append(" {");

        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;
                    }
                }
                // 如果發(fā)現(xiàn)方法中的參數(shù)有一個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);
                }
                //  如果沒有發(fā)現(xiàn)硕噩,則會尋找每一個參數(shù)類型中的屬性是否有為URL類型的
                else {
                    String attribMethod = null;

                    // find URL getter method
                    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 adaptive 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();
                // value is not set, use the value generated from class name as the 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("org.apache.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]))
                           //如果方法參數(shù)類型名稱為"org.apache.dubbo.rpc.Invocation"則從url獲取以此參數(shù)類型名為key的值,獲取不到則取默認擴展名炉擅,即Protocol接口上注解SPI的值“dubbo”
                            if (hasInvocation)
                                getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName);
                            else
                               //否則辉懒,取從url中取以方法上注解adaptive的值為key對應(yīng)的值
                                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(");");
            }

            codeBuilder.append("\npublic ").append(rt.getCanonicalName()).append(" ").append(method.getName()).append("(");
            for (int i = 0; i < pts.length; i++) {
                if (i > 0) {
                    codeBuilder.append(", ");
                }
                codeBuilder.append(pts[i].getCanonicalName());
                codeBuilder.append(" ");
                codeBuilder.append("arg").append(i);
            }
            codeBuilder.append(")");
            if (ets.length > 0) {
                codeBuilder.append(" throws ");
                for (int i = 0; i < ets.length; i++) {
                    if (i > 0) {
                        codeBuilder.append(", ");
                    }
                    codeBuilder.append(ets[i].getCanonicalName());
                }
            }
            codeBuilder.append(" {");
            codeBuilder.append(code.toString());
            codeBuilder.append("\n}");
        }
        codeBuilder.append("\n}");
        if (logger.isDebugEnabled()) {
            logger.debug(codeBuilder.toString());
        }
        return codeBuilder.toString();
    }

我們來看下生成的插件類Protocol$Adaptive代碼:

package org.apache.dubbo.rpc;
import org.apache.dubbo.common.extension.ExtensionLoader;
public class Protocol$Adaptive implements org.apache.dubbo.rpc.Protocol {

public void destroy() 
{throw new UnsupportedOperationException("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("method public abstract int org.apache.dubbo.rpc.Protocol.getDefaultPort() 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("Fail 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 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("Fail 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);
}
}

可以看出此類可以根據(jù)url中參數(shù)protocol值加載對應(yīng)的插件,如果url中沒有谍失,則加載名為"dubbo"對應(yīng)的插件眶俩,而從前面加載的四個插件可以看出,名稱為dubbo的插件類為org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol.

寫到這里總算將SPI加載的過程大體上講述了一篇快鱼,Dubbo中還有許多類似的插件颠印,原理基本相同;除了有的插口有自適應(yīng)插件抹竹,比如org.apache.dubbo.common.compiler.Compilerorg.apache.dubbo.common.extension.ExtensionFactory,自適應(yīng)插件類上都有注解@Adaptive线罕,比如Compile的自適應(yīng)插件AdaptiveCompiler,ExtensionFactory的自適應(yīng)插件AdaptiveExtensionFactory.

為什么要提供自適應(yīng)插件,而不是都在運行時生成窃判?

答:
(1)解決雞生蛋钞楼,蛋生雞的問題,上面createAdaptiveExtensionClass方法中袄琳,在第1步生成Protocol$Adaptive類后询件,會使用編譯器將其編譯成字節(jié)碼,但是編譯器本身也是插件化的唆樊,可以有好幾種編譯器宛琅,所以需要提供一個已經(jīng)存在的自適應(yīng)編譯器(AdaptiveCompiler),然后在編譯的時候逗旁,使用此編譯器找到Compile接口上SPI注解中配置的默認的編譯器進行編譯夯秃。
(2)解決對象生成方式不同導(dǎo)致的加載問題;Dubbo中對象的生成一類是由Spring容器創(chuàng)建,一類是根據(jù)插件文件的配置動態(tài)加載仓洼;所以要想獲取這兩部分對象介陶,需要使用不同的方式;而AdaptiveExtensionFactory就是為了解決這個問題色建,在獲取對象時哺呜,分別從Spring容器和ExtensionLoader中查找。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末箕戳,一起剝皮案震驚了整個濱河市某残,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌陵吸,老刑警劉巖玻墅,帶你破解...
    沈念sama閱讀 211,123評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異壮虫,居然都是意外死亡澳厢,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評論 2 384
  • 文/潘曉璐 我一進店門囚似,熙熙樓的掌柜王于貴愁眉苦臉地迎上來剩拢,“玉大人,你說我怎么就攤上這事饶唤⌒旆ィ” “怎么了?”我有些...
    開封第一講書人閱讀 156,723評論 0 345
  • 文/不壞的土叔 我叫張陵募狂,是天一觀的道長办素。 經(jīng)常有香客問我,道長祸穷,這世上最難降的妖魔是什么摸屠? 我笑而不...
    開封第一講書人閱讀 56,357評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮粱哼,結(jié)果婚禮上季二,老公的妹妹穿的比我還像新娘。我一直安慰自己揭措,他們只是感情好胯舷,可當(dāng)我...
    茶點故事閱讀 65,412評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著绊含,像睡著了一般桑嘶。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上躬充,一...
    開封第一講書人閱讀 49,760評論 1 289
  • 那天逃顶,我揣著相機與錄音讨便,去河邊找鬼。 笑死以政,一個胖子當(dāng)著我的面吹牛霸褒,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播盈蛮,決...
    沈念sama閱讀 38,904評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼废菱,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了抖誉?” 一聲冷哼從身側(cè)響起殊轴,我...
    開封第一講書人閱讀 37,672評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎袒炉,沒想到半個月后旁理,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,118評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡我磁,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,456評論 2 325
  • 正文 我和宋清朗相戀三年孽文,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片十性。...
    茶點故事閱讀 38,599評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡叛溢,死狀恐怖塑悼,靈堂內(nèi)的尸體忽然破棺而出劲适,到底是詐尸還是另有隱情,我是刑警寧澤厢蒜,帶...
    沈念sama閱讀 34,264評論 4 328
  • 正文 年R本政府宣布霞势,位于F島的核電站,受9級特大地震影響斑鸦,放射性物質(zhì)發(fā)生泄漏愕贡。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,857評論 3 312
  • 文/蒙蒙 一巷屿、第九天 我趴在偏房一處隱蔽的房頂上張望固以。 院中可真熱鬧,春花似錦嘱巾、人聲如沸憨琳。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,731評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽篙螟。三九已至,卻和暖如春问拘,著一層夾襖步出監(jiān)牢的瞬間遍略,已是汗流浹背惧所。 一陣腳步聲響...
    開封第一講書人閱讀 31,956評論 1 264
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留绪杏,地道東北人下愈。 一個月前我還...
    沈念sama閱讀 46,286評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像寞忿,于是被迫代替她去往敵國和親驰唬。 傳聞我的和親對象是個殘疾皇子蜘欲,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,465評論 2 348

推薦閱讀更多精彩內(nèi)容

  • Dubbo采用微內(nèi)核+插件體系昔逗,使得設(shè)計優(yōu)雅佑刷,擴展性強轮蜕。那所謂的微內(nèi)核+插件體系是如何實現(xiàn)的呢娇钱!大家是否熟悉spi...
    carl_zhao閱讀 934評論 1 3
  • 0 前言 站在一個框架作者的角度來說娇昙,定義一個接口旁钧,自己默認給出幾個接口的實現(xiàn)類骗露,同時 允許框架的使用者也能夠自定...
    七寸知架構(gòu)閱讀 16,207評論 3 67
  • 前面我們了解過了Java的SPI擴展機制杯拐,對于Java擴展機制的原理以及優(yōu)缺點也有了大概的了解霞篡,這里繼續(xù)深入一下D...
    加大裝益達閱讀 5,053評論 2 20
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)端逼,斷路器朗兵,智...
    卡卡羅2017閱讀 134,628評論 18 139
  • 總結(jié):Protocolrefprotocol = ExtensionLoader.getExtensionLoad...
    Ngcc閱讀 703評論 0 2