開篇
?本文嘗試通過一個示例來講解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)識的名字,其次取接口名分割。