Dubbo Adaptive機(jī)制詳解實(shí)戰(zhàn)

開篇

?本文嘗試通過一個示例來講解Adaptive機(jī)制的用法摆出,然后會從源碼的角度對其實(shí)現(xiàn)原理進(jìn)行講解


Adaptive機(jī)制

?對應(yīng)于Adaptive機(jī)制躯肌,Dubbo提供了一個注解@Adaptive嘹承,該注解可以用于接口的某個實(shí)現(xiàn)類上,也可以用于接口方法上。如果用在接口的子類上辉巡,則表示Adaptive機(jī)制的實(shí)現(xiàn)會按照該子類的方式進(jìn)行自定義實(shí)現(xiàn);如果用在方法上搅荞,則表示Dubbo會為該接口自動生成一個子類红氯,并且按照一定的格式重寫該方法框咙,而其余沒有標(biāo)注@Adaptive注解的方法將會默認(rèn)拋出異常。

?對于第一種Adaptive的使用方式痢甘,Dubbo里只有ExtensionFactory接口使用了喇嘱,其有一個子類AdaptiveExtensionFactory就使用了@Adaptive注解進(jìn)行了標(biāo)注,主要作用就是在獲取目標(biāo)對象時(shí)塞栅,分別通過ExtensionLoader和Spring容器兩種方式獲取,主要用在SPI的IOC實(shí)現(xiàn)中用于獲取上下文的Bean對象者铜。

?對于第二種使用@Adaptive注解標(biāo)注在接口方法上以實(shí)現(xiàn)Adaptive機(jī)制的使用原理,通過一個下面的例子進(jìn)行說明放椰。


用法示例

例子說明

  • 定義PlantsWater的接口并通過@SPI注解進(jìn)行注解作烟,注解可選擇帶默認(rèn)值。

  • 將watering()方法使用@Adaptive注解進(jìn)行了標(biāo)注砾医,表示該方法在自動生成的子類中是需要動態(tài)實(shí)現(xiàn)的方法拿撩。

  • 增加grant()方法是為了表明不帶@Adaptive在自動生成的子類方法內(nèi)部會拋出異常。

  • 為PlantsWater增加兩個實(shí)現(xiàn)如蚜,AppleWater和BananaWater压恒,實(shí)際調(diào)用通過參數(shù)控制。

  • 在META-INF/dubbo下創(chuàng)建一個文件错邦,該文件的名稱是目標(biāo)接口的全限定名探赫,這里是org.apache.dubbo.spi.example.PlantsWater,在該文件中需要指定該接口所有可提供服務(wù)的子類撬呢。

  • 定義主函數(shù)ExtensionLoaderDemo模擬SPI調(diào)用的驗(yàn)證伦吠。

----定義基礎(chǔ)應(yīng)用類

public interface Fruit {}
public class Apple implements Fruit {}
public class Banana implements Fruit{}



----定義SPI類

@SPI("banana")
public interface PlantsWater {

    Fruit grant();

    @Adaptive
    String watering(URL url);
}


public class AppleWater implements PlantsWater {
    public Fruit grant() {
        return new Apple();
    }

    public String watering(URL url) {
        System.out.println("watering apple");
        return "watering finished";
    }
}


public class BananaWater implements PlantsWater {

    public Fruit grant() {
        return new Banana();
    }

    public String watering(URL url) {
        System.out.println("watering banana");
        return "watering success";
    }
}



----resources文件 org.apache.dubbo.spi.example.PlantsWater

apple=org.apache.dubbo.spi.example.AppleWater
banana=org.apache.dubbo.spi.example.BananaWater


------測試代碼內(nèi)容

public class ExtensionLoaderDemo {

    public static void main(String[] args) {
        // 首先創(chuàng)建一個模擬用的URL對象
        URL url = URL.valueOf("dubbo://192.168.0.101:20880?plants.water=apple");
        // 通過ExtensionLoader獲取一個PlantsWater對象,getAdaptiveExtension已經(jīng)加載了所有SPI類
        PlantsWater plantsWater = ExtensionLoader.getExtensionLoader(PlantsWater.class)
                .getAdaptiveExtension();
        // 使用該P(yáng)lantsWater調(diào)用其"自適應(yīng)標(biāo)注的"方法,獲取調(diào)用結(jié)果
        String result = plantsWater.watering(url);
        System.out.println(result);
    }
}


-----實(shí)際輸出內(nèi)容

十月 11, 2019 7:48:51 下午 org.apache.dubbo.common.logger.LoggerFactory info
信息: using logger: org.apache.dubbo.common.logger.jcl.JclLoggerAdapter
watering apple
watering finished

Process finished with exit code 0

原理補(bǔ)充

  • 這里提供了AppleWater和BananaWater表示的其實(shí)是兩種基礎(chǔ)服務(wù)類魂拦,本質(zhì)上它們?nèi)叩年P(guān)系是PlantsWater用于對外提供一個規(guī)范毛仪,而AppleWater和BananaWater則是實(shí)現(xiàn)了這種規(guī)范的兩種基礎(chǔ)服務(wù)。至于調(diào)用方需要使用哪種基礎(chǔ)服務(wù)來實(shí)現(xiàn)其功能晨另,這就需要根據(jù)調(diào)用方指定的參數(shù)來動態(tài)選取的潭千,而@Adaptive機(jī)制就是提供了這樣一種選取功能。

  • 模擬調(diào)用構(gòu)造了一個URL對象借尿,這個URL對象是Dubbo中進(jìn)行參數(shù)傳遞所使用的一個基礎(chǔ)類刨晴,在配置文件中配置的屬性都會被封裝到該對象中。這里我們主要要注意該對象是通過一個url構(gòu)造的路翻,并且url的最后我們有一個參數(shù)plants.water=apple狈癞,這里其實(shí)就是我們所指定的使用哪種基礎(chǔ)服務(wù)類的參數(shù)。比如這里指定的就是使用apple對應(yīng)的AppleWater茂契。

  • ExtensionLoader.getExtensionLoader(PlantsWater.class).getAdaptiveExtension()的過程中會生成PlantsWater類對應(yīng)的ExtensionLoader蝶桶,在該Loader里面保存了PlantsWater對應(yīng)的SPI的各個具體實(shí)現(xiàn)類。


PlantsWater$Adaptive

說明:

  • PlantsWater$Adaptive是PlantsWater接口動態(tài)生成的子類掉冶,通過ExtensionLoader.getAdaptiveExtension()方法動態(tài)生成真竖。

  • 針對沒有@Adaptive修飾的方法脐雪,子類中實(shí)現(xiàn)的方法體內(nèi)部直接拋出UnsupportedOperationException。

  • 針對有@Adaptive修飾的方法恢共,子類會重新實(shí)現(xiàn)動態(tài)獲取PlantsWater 實(shí)例的方法战秋,ExtensionLoader.getExtensionLoader(PlantsWater.class).getExtension(extName)。

  • 在使用@Adaptive注解標(biāo)注的方法中讨韭,其參數(shù)中必須有一個參數(shù)類型為URL脂信,或者其某個參數(shù)提供了某個方法,該方法可以返回一個URL對象透硝。

  • 在方法的實(shí)現(xiàn)中會通過URL對象獲取某個參數(shù)對應(yīng)的參數(shù)值狰闪,如果在接口的@SPI注解中指定了默認(rèn)值,那么在使用URL對象獲取參數(shù)值時(shí)濒生,如果沒有取到埋泵,就會使用該默認(rèn)值。url.getParameter("plants.water", "banana")中plants.water取URL中帶的參數(shù)罪治,"banana"屬于SPI指定的默認(rèn)值秋泄。

  • 根據(jù)獲取到的參數(shù)值,在ExtensionLoader中獲取該參數(shù)值對應(yīng)的服務(wù)提供類對象(通過ExtensionLoader
    .getExtensionLoader().getExtension(extName)
    )规阀,然后將真正的調(diào)用委托給該服務(wù)提供類對象進(jìn)行。

  • 在通過URL對象獲取參數(shù)時(shí)瘦麸,參數(shù)key獲取的對應(yīng)規(guī)則是谁撼,首先會從@Adaptive注解的參數(shù)值中獲取,如果該注解沒有指定參數(shù)名滋饲,那么就會默認(rèn)將目標(biāo)接口的類名轉(zhuǎn)換為點(diǎn)分形式作為參數(shù)名厉碟,比如這里PlantsWater轉(zhuǎn)換為點(diǎn)分形式就是plants.water。

package org.apache.dubbo.spi.example;
import org.apache.dubbo.common.extension.ExtensionLoader;

public class PlantsWater$Adaptive implements org.apache.dubbo.spi.example.PlantsWater {

    public java.lang.String watering(org.apache.dubbo.common.URL arg0)  {
        if (arg0 == null) throw new IllegalArgumentException("url == null");
        
        org.apache.dubbo.common.URL url = arg0;
        String extName = url.getParameter("plants.water", "banana");
        
        if(extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.spi.example.PlantsWater) name from url (" + url.toString() + ") use keys([plants.water])");
        
        org.apache.dubbo.spi.example.PlantsWater extension = (org.apache.dubbo.spi.example.PlantsWater)ExtensionLoader
                                             .getExtensionLoader(org.apache.dubbo.spi.example.PlantsWater.class)
                                             .getExtension(extName);
        
        return extension.watering(arg0);
    }

    public org.apache.dubbo.spi.example.Fruit grant()  {
        throw new UnsupportedOperationException("The method public abstract org.apache.dubbo.spi.example.Fruit org.apache.dubbo.spi.example.PlantsWater.grant() of interface org.apache.dubbo.spi.example.PlantsWater is not adaptive method!");
    }
}


實(shí)現(xiàn)原理

Dubbo Adaptive的實(shí)現(xiàn)機(jī)制根據(jù)上面的講解其實(shí)步驟已經(jīng)比較清晰了屠缭,主要分為如下三個步驟:

  • 1箍鼓、加載標(biāo)注有@Adaptive注解的接口,如果不存在呵曹,則不支持Adaptive機(jī)制款咖;

  • 2、為目標(biāo)接口按照一定的模板生成子類代碼奄喂,并且編譯生成的代碼铐殃,然后通過反射生成該類的對象;

  • 3跨新、結(jié)合生成的對象實(shí)例富腊,通過傳入的URL對象,獲取指定key的配置域帐,然后加載該key對應(yīng)的類對象赘被,最終將調(diào)用委托給該類對象進(jìn)行是整。

可以看到,通過這種方式民假,Dubbo就實(shí)現(xiàn)了一種通過配置參數(shù)動態(tài)選擇所使用的服務(wù)的目的浮入,而實(shí)現(xiàn)這種機(jī)制的入口主要在ExtensionLoader.getAdaptiveExtension()方法

public class ExtensionLoaderDemo {

    public static void main(String[] args) {
        // 首先創(chuàng)建一個模擬用的URL對象
        URL url = URL.valueOf("dubbo://192.168.0.101:20880?plants.water=apple");
        // 通過ExtensionLoader獲取一個FruitGranter對象
        PlantsWater plantsWater = ExtensionLoader.getExtensionLoader(PlantsWater.class)
                .getAdaptiveExtension();
        // 使用該FruitGranter調(diào)用其"自適應(yīng)標(biāo)注的"方法,獲取調(diào)用結(jié)果
        String result = plantsWater.watering(url);
        System.out.println(result);
    }
}

說明:

  • 上面的代碼作為整個動態(tài)生成代碼的入口阳欲,關(guān)注ExtensionLoader.getExtensionLoader(PlantsWater.class)
    .getAdaptiveExtension()就可以了舵盈。


public T getAdaptiveExtension() {
  Object instance = cachedAdaptiveInstance.get();
  if (instance == null) {
    if (createAdaptiveInstanceError == null) {
      synchronized (cachedAdaptiveInstance) {
        instance = cachedAdaptiveInstance.get();
        if (instance == null) {
          try {
            // 創(chuàng)建Adaptive實(shí)例
            instance = createAdaptiveExtension();
            cachedAdaptiveInstance.set(instance);
          } catch (Throwable t) {
            createAdaptiveInstanceError = t;
            throw new IllegalStateException("Failed to create adaptive " 
                + "instance: " + t.toString(), t);
          }
        }
      }
    } else {
      throw new IllegalStateException("Failed to create adaptive instance: " 
          + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError);
    }
  }

  return (T) instance;
}

說明:
上面的代碼首先通過雙檢查法來從緩存中獲取Adaptive實(shí)例,如果沒獲取到球化,則創(chuàng)建一個秽晚。我們這里繼續(xù)看createAdaptiveExtension()方法的實(shí)現(xiàn)。


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

說明:
上面代碼是創(chuàng)建Adaptive實(shí)例的方法是一個主干方法筒愚,從這里調(diào)用方法的順序就可以看出其主要作用:

  • 獲取一個Adaptive類的class對象赴蝇,不存在則創(chuàng)建一個,該方法會保證一定存在一個該class對象巢掺;
  • 通過反射創(chuàng)建一個Adaptive類的實(shí)例句伶;
  • 對創(chuàng)建的Adaptive注入相關(guān)屬性,需要注意的是陆淀,Dubbo目前只支持通過setter方法注入屬性考余。


    private Class<?> getAdaptiveExtensionClass() {

  // 通過讀取Dubbo的配置文件,獲取其中的SPI類轧苫,其主要處理了四部分的類:
  // 1. 標(biāo)注了@Activate注解的類楚堤,該注解的主要作用是將某個實(shí)現(xiàn)子類標(biāo)注為自動激活,也就是在加載
  //    實(shí)例的時(shí)候也會加載該類的對象含懊;

  // 2. 記錄目標(biāo)接口是否標(biāo)注了@Adaptive注解身冬,如果標(biāo)注了該注解,則表示需要為該接口動態(tài)生成子類岔乔,或者說
  //    目標(biāo)接口是否存在標(biāo)注了@Adaptive注解的子類酥筝,如果存在,則直接使用該子類作為Adaptive類雏门;
  
  // 3. 檢查加載到的類是否包含有傳入目標(biāo)接口參數(shù)的構(gòu)造方法嘿歌,如果是,則表示該類是一個代理類剿配,也可以
  //    將其理解為最終會被作為責(zé)任鏈進(jìn)行調(diào)用的類搅幅,這些類最終會在目標(biāo)類被調(diào)用的時(shí)候以類似于AOP的方式,
  //    將目標(biāo)類包裹起來呼胚,然后將包裹之后的類對外提供服務(wù)茄唐;

  // 4. 剩余的一般類就是實(shí)現(xiàn)了目標(biāo)接口,并且作為基礎(chǔ)服務(wù)提供的類。

        getExtensionClasses();



  // 經(jīng)過上面的類加載過程沪编,如果目標(biāo)接口某個子類存在@Adaptive注解呼盆,就會將其class對象緩存到
  // cachedAdaptiveClass對象中。這里我們就可以看到@Adaptive注解的兩種使用方式的分界點(diǎn)蚁廓,也就是說访圃,
  // 如果某個子類標(biāo)注了@Adaptive注解,那么就會使用該子類所自定義的Adaptive機(jī)制相嵌,如果沒有子類標(biāo)注了
  // 該注解腿时,那么就會使用下面的createAdaptiveExtensionClass()方式來創(chuàng)建一個目標(biāo)類class對象
        if (cachedAdaptiveClass != null) {
            return cachedAdaptiveClass;
        }

  // 創(chuàng)建一個目標(biāo)接口的子類class對象
        return cachedAdaptiveClass = createAdaptiveExtensionClass();
    }


    private Class<?> createAdaptiveExtensionClass() {
  // 為目標(biāo)接口生成子類代碼,以字符串形式表示
        String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();

  // 獲取classloader
        ClassLoader classLoader = findClassLoader();

  // 通過jdk或者javassist的方式編譯生成的子類字符串(默認(rèn)是javassist)饭宾,從而得到一個class對象
        org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
        return compiler.compile(code, classLoader);
    }

說明:
上面的代碼中主要是一個骨架代碼批糟,首先通過getExtensionClasses()獲取配置文件中配置的各個類對象,其加載的原理可閱讀文章Dubbo之SPI原理詳解看铆;加載完成后徽鼎,會通過AdaptiveClassCodeGenerator來為目標(biāo)類生成子類代碼,并以字符串的形式返回弹惦,最后通過javassist或jdk的方式進(jìn)行編譯然后返回class對象否淤。這里我們主要閱讀AdaptiveClassCodeGenerator.generate()方法是如何生成目標(biāo)接口的子類的。


   public String generate() {
        //判斷目標(biāo)接口是否有方法標(biāo)注了@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()); // 生成package信息
        code.append(generateImports()); // 生成import信息石抡,這里只導(dǎo)入了ExtensionLoader類,其余的類都通過全限定名的方式來使用
        code.append(generateClassDeclaration()); // 生成類聲明信息
        
        Method[] methods = type.getMethods();
        for (Method method : methods) {
            code.append(generateMethod(method)); // 為各個方法生成實(shí)現(xiàn)方法信息
        }
        code.append("}");
        
        if (logger.isDebugEnabled()) {
            logger.debug(code.toString());
        }
        return code.toString(); // 返回生成的class代碼
    }

說明:
上面代碼的generate()方法是生成目標(biāo)類的主干方法助泽,其主要分為如下幾個步驟:

  • 生成package信息汁雷;
  • 生成import信息;
  • 生成類聲明信息报咳;
  • 生成各個方法的實(shí)現(xiàn);
    前面幾個步驟實(shí)現(xiàn)原理都相較比較簡單挖藏,繼續(xù)閱讀generateMethod()方法的實(shí)現(xiàn)原理暑刃。


    private String generateMethod(Method method) {
        String methodReturnType = method.getReturnType().getCanonicalName(); // 生成返回值信息
        String methodName = method.getName(); // 生成方法名信息
        String methodContent = generateMethodContent(method); // 生成方法體信息
        String methodArgs = generateMethodArguments(method); // 生成方法參數(shù)信息
        String methodThrows = generateMethodThrows(method); // 生成異常信息
        return String.format(CODE_METHOD_DECLARATION, methodReturnType, methodName, methodArgs, methodThrows, methodContent); // 對方法進(jìn)行格式化返回
    }

說明:
上面代碼的方法的生成,也拆分成了幾個子步驟膜眠,主要包括:

  • 生成返回值信息岩臣;
  • 生成方法名信息;
  • 生成方法參數(shù)信息宵膨;
  • 生成方法的異常信息架谎;
  • 生成方法體信息;
    需要注意的是辟躏,這里所使用的所有類都是使用的其全限定類名谷扣,通過前面我們展示的PlantsWater的子類代碼也可以看出這一點(diǎn)。上面生成的信息中,方法的返回值会涎,方法名裹匙,方法參數(shù)以及異常信息都可以通過接口聲明獲取到,而方法體則需要根據(jù)一定的邏輯來生成末秃。關(guān)于方法參數(shù)概页,需要說明的是,Dubbo并沒有使用接口中對應(yīng)參數(shù)的名稱练慕,而是對每一個參數(shù)的參數(shù)名依次使用arg0惰匙、arg1等名稱。這里我們繼續(xù)閱讀Dubbo生成方法體內(nèi)容的代碼铃将。


    private String generateMethodContent(Method method) {

        // 獲取方法上標(biāo)注的@Adaptive注解项鬼,前面講到,Dubbo會使用該注解的值作為動態(tài)參數(shù)的key值
        Adaptive adaptiveAnnotation = method.getAnnotation(Adaptive.class);
        StringBuilder code = new StringBuilder(512);

        // 如果當(dāng)前方法沒有標(biāo)注@Adaptive注解麸塞,該方法的實(shí)現(xiàn)就會默認(rèn)拋出異常
        if (adaptiveAnnotation == null) {
            return generateUnsupported(method);
        } else {

            // 獲取參數(shù)中類型為URL的參數(shù)所在的參數(shù)索引位置秃臣,因?yàn)槲覀兊膮?shù)都是通過arg[i]的形式編排的,因而
            // 獲取其索引就可以得到該參數(shù)的引用哪工。這里URL參數(shù)的主要作用是獲取目標(biāo)參數(shù)對應(yīng)的參數(shù)值
            int urlTypeIndex = getUrlTypeIndex(method);
            
            if (urlTypeIndex != -1) {
                // 如果參數(shù)中存在URL類型的參數(shù)奥此,那么就為該參數(shù)進(jìn)行空值檢查,如果為空雁比,則拋出異常
                code.append(generateUrlNullCheck(urlTypeIndex));
            } else {
                // 如果參數(shù)中不存在URL類型的參數(shù)稚虎,那么就會檢查每個參數(shù),判斷其是否有某個方法的返回值是URL類型偎捎,
                // 如果存在該方法蠢终,則首先對該參數(shù)進(jìn)行空指針檢查,如果為空則拋出異常茴她。然后調(diào)用該對象的目標(biāo)方法寻拂,
                // 以獲取到一個URL對象,然后對獲取到的URL對象進(jìn)行空值檢查丈牢,為空也會拋出異常祭钉。
                code.append(generateUrlAssignmentIndirectly(method));
            }

            // 這里主要是獲取@Adaptive注解的參數(shù),如果沒有配置己沛,就會使用目標(biāo)接口的類型由駝峰形式轉(zhuǎn)換為點(diǎn)分形式
            // 的名稱作為將要獲取的參數(shù)值的key名稱慌核,比如前面的PlantsWater轉(zhuǎn)換后為plants.water。
            // 這里需要注意的是申尼,返回值是一個數(shù)組類型垮卓,這是因?yàn)镈ubbo會通過嵌套獲取的方式來的到目標(biāo)參數(shù),
            // 比如我們使用了@Adaptive({"client", "transporter"})的形式师幕,那么最終就會在URL對象中獲取兩次
            // 參數(shù)粟按,如String extName = url.getParameter("client", url.getParameter("transporter"))
            String[] value = getMethodAdaptiveValue(adaptiveAnnotation);

            // 判斷是否存在Invocation類型的參數(shù)
            boolean hasInvocation = hasInvocationArgument(method);
            
            // 為Invocation類型的參數(shù)添加空值檢查的邏輯
            code.append(generateInvocationArgumentNullCheck(method));
            
            // 生成獲取extName的邏輯,也即前面通過String[] value生成的通過url.getParameter()的
            // 邏輯代碼,最終會得到用戶配置的擴(kuò)展的名稱钾怔,從而對應(yīng)某個基礎(chǔ)服務(wù)類
            code.append(generateExtNameAssignment(value, hasInvocation));

            // 為extName添加空值檢查代碼
            code.append(generateExtNameNullCheck(value));
            
            // 通過extName在ExtensionLoader中獲取其對應(yīng)的基礎(chǔ)服務(wù)類碱呼,比如前面的PlantsWater,在這里就是
            // PlantsWater extension = ExtensionLoader.getExtensionLoader(ExtensionLoader.class)
            // .getExtension(extName)宗侦,這樣就得到了一個PlantsWater的實(shí)例對象
            code.append(generateExtensionAssignment());

            // 生成目標(biāo)實(shí)例的當(dāng)前方法的調(diào)用邏輯愚臀,然后將結(jié)果返回。比如PlantsWater就是
            // return extension.watering(arg0);
            // 這里方法名就是當(dāng)前實(shí)現(xiàn)的方法的名稱矾利,而參數(shù)就是當(dāng)前方法傳入的參數(shù)姑裂,
            // 就是目標(biāo)接口中的同一方法,而方法參數(shù)前面已經(jīng)講到男旗,都是使用arg[i]的形式命名的舶斧,因而這里直接
            // 將其依次羅列出來即可
            code.append(generateReturnAndInvocation(method));
        }
        
        // 將生成的代碼返回
        return code.toString();
    }



    private String[] getMethodAdaptiveValue(Adaptive adaptiveAnnotation) {
        // 獲取@Adaptive注解的配置獲取目標(biāo)參數(shù)的key值
        String[] value = adaptiveAnnotation.value();
        // @Adaptive沒有注解,就通過Interface定義的名字如PlantsWater按字母分割生成plantswater 
        if (value.length == 0) {
            String splitName = StringUtils.camelToSplitName(type.getSimpleName(), ".");
            value = new String[]{splitName};
        }
        return value;
    }

說明:
上面的邏輯主要分為了如下幾個步驟:

  • 判斷當(dāng)前方法是否標(biāo)注了@Adaptive注解察皇,如果沒有標(biāo)注茴厉,則為其生成一個默認(rèn)實(shí)現(xiàn),該實(shí)現(xiàn)中會默認(rèn)拋出異常什荣,也就是說只有使用@Adaptive注解標(biāo)注的方法才是作為自適應(yīng)機(jī)制的方法矾缓;

  • 獲取方法參數(shù)中類型為URL的參數(shù),如果不存在稻爬,則獲取參數(shù)中某個存在可以返回URL類型對象的方法的參數(shù)嗜闻,并且調(diào)用該方法獲取URL參數(shù);

  • 通過@Adaptive注解的配置獲取目標(biāo)參數(shù)的key值桅锄,然后通過前面得到的URL參數(shù)獲取該key對應(yīng)的參數(shù)值琉雳,從而得到了基礎(chǔ)服務(wù)類對應(yīng)的名稱;

  • 通過ExtensionLoader獲取該名稱對應(yīng)的基礎(chǔ)服務(wù)類實(shí)例友瘤;

  • 通過調(diào)用基礎(chǔ)服務(wù)類的實(shí)例的當(dāng)前方法來實(shí)現(xiàn)最終的基礎(chǔ)服務(wù)翠肘。

可以看到,這里實(shí)現(xiàn)的自適應(yīng)機(jī)制邏輯結(jié)構(gòu)是非常清晰的辫秧,讀者通過閱讀這里的源碼也就比較好的理解了Dubbo所提供的自適應(yīng)機(jī)制的原理锯茄,也能夠比較好的通過自適應(yīng)機(jī)制來完成某些定制化的工作。


補(bǔ)充

在Adaptive擴(kuò)展機(jī)制實(shí)現(xiàn)當(dāng)中茶没,有幾個變量有必要梳理一下。

extName的獲取優(yōu)先級:@SPI("xxx")和URL.getParameter("yyy","xxx")晚碾,如果URL中帶有變量標(biāo)識則優(yōu)先取URL抓半,否則取@SPI中的變量。

AdaptiveValue的獲取格嘁,@Adaptive("zzz") 和 interface PlantsWater當(dāng)中的PlantsWater接口名笛求,優(yōu)先以@Adaptive標(biāo)識的名字,其次取接口名分割。


參考

Dubbo Adaptive機(jī)制詳解

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末探入,一起剝皮案震驚了整個濱河市狡孔,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌蜂嗽,老刑警劉巖苗膝,帶你破解...
    沈念sama閱讀 221,695評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異植旧,居然都是意外死亡辱揭,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評論 3 399
  • 文/潘曉璐 我一進(jìn)店門病附,熙熙樓的掌柜王于貴愁眉苦臉地迎上來问窃,“玉大人,你說我怎么就攤上這事完沪∮虮樱” “怎么了?”我有些...
    開封第一講書人閱讀 168,130評論 0 360
  • 文/不壞的土叔 我叫張陵覆积,是天一觀的道長听皿。 經(jīng)常有香客問我,道長技健,這世上最難降的妖魔是什么写穴? 我笑而不...
    開封第一講書人閱讀 59,648評論 1 297
  • 正文 為了忘掉前任,我火速辦了婚禮雌贱,結(jié)果婚禮上啊送,老公的妹妹穿的比我還像新娘。我一直安慰自己欣孤,他們只是感情好馋没,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,655評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著降传,像睡著了一般篷朵。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上婆排,一...
    開封第一講書人閱讀 52,268評論 1 309
  • 那天声旺,我揣著相機(jī)與錄音,去河邊找鬼段只。 笑死腮猖,一個胖子當(dāng)著我的面吹牛呜叫,可吹牛的內(nèi)容都是我干的感昼。 我是一名探鬼主播,決...
    沈念sama閱讀 40,835評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼塌鸯,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了姐赡?” 一聲冷哼從身側(cè)響起莱预,我...
    開封第一講書人閱讀 39,740評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎项滑,沒想到半個月后依沮,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,286評論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡杖们,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,375評論 3 340
  • 正文 我和宋清朗相戀三年悉抵,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片摘完。...
    茶點(diǎn)故事閱讀 40,505評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡姥饰,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出孝治,到底是詐尸還是另有隱情列粪,我是刑警寧澤,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布谈飒,位于F島的核電站岂座,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏杭措。R本人自食惡果不足惜费什,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,873評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望手素。 院中可真熱鬧鸳址,春花似錦、人聲如沸泉懦。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,357評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽崩哩。三九已至巡球,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間邓嘹,已是汗流浹背酣栈。 一陣腳步聲響...
    開封第一講書人閱讀 33,466評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留汹押,地道東北人矿筝。 一個月前我還...
    沈念sama閱讀 48,921評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像鲸阻,于是被迫代替她去往敵國和親跋涣。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,515評論 2 359

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