上篇我們簡單講了dubbo Adaptive的使用参萄,沒有太多代碼,一個(gè)接口和幾個(gè)實(shí)現(xiàn)類煎饼,還有一個(gè)配置文件和測試類讹挎。接口和實(shí)現(xiàn)類沒有什么講的,因此入口就從測試類開始吆玖。
測試代碼如下:
// 1筒溃、獲取 extExtensionLoader
ExtensionLoader<AdaptiveExt> extExtensionLoader = ExtensionLoader.getExtensionLoader(AdaptiveExt.class);
// 2、獲取自適應(yīng)擴(kuò)展點(diǎn)
AdaptiveExt adaptiveExt = extExtensionLoader.getAdaptiveExtension();
// 3衰伯、URL參數(shù)
URL url = URL.valueOf("test://localhost/test?adaptive.ext=spring");
// 4铡羡、調(diào)用
System.out.println(adaptiveExt.echo("d", url));
-
ExtensionLoader.getExtensionLoader
已經(jīng)講解過积蔚,現(xiàn)在我們直接看getAdaptiveExtension
方法意鲸,代碼如下:
/**
* 獲取自適應(yīng)擴(kuò)展的instance
*/
@SuppressWarnings("unchecked")
public T getAdaptiveExtension() {
// 從緩存中獲取自適應(yīng)擴(kuò)展instance
Object instance = cachedAdaptiveInstance.get();
// 沒有命中緩存
if (instance == null) {
// 創(chuàng)建自適應(yīng)擴(kuò)展instance異常為空
if (createAdaptiveInstanceError == null) {
// 加鎖,雙重檢查
synchronized (cachedAdaptiveInstance) {
instance = cachedAdaptiveInstance.get();
// 繼續(xù)沒有命中緩存
if (instance == null) {
try {
// 創(chuàng)建自適應(yīng)擴(kuò)展instance
instance = createAdaptiveExtension();
// set
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);
}
}
// 返回自適應(yīng)擴(kuò)展instance
return (T) instance;
}
getAdaptiveExtension
方法首先會(huì)檢查緩存尽爆,緩存未命中怎顾,則調(diào)用 createAdaptiveExtension
方法創(chuàng)建自適應(yīng)拓展。createAdaptiveExtension
代碼如下:
/**
* 創(chuàng)建自適應(yīng)擴(kuò)展instance
*/
@SuppressWarnings("unchecked")
private T createAdaptiveExtension() {
try {
// 獲取自適應(yīng)拓展類漱贱,并通過反射實(shí)例化槐雾,然后就是dubbo IOC 調(diào)用 injectExtension 方法向拓展實(shí)例中注入依賴
return injectExtension((T) getAdaptiveExtensionClass().newInstance());
} catch (Exception e) {
throw new IllegalStateException("Can't create adaptive extension " + type + ", cause: " + e.getMessage(), e);
}
}
createAdaptiveExtension
方法的代碼比較少,但卻包含了三個(gè)邏輯幅狮,分別如下:
- 調(diào)用 getAdaptiveExtensionClass 方法獲取自適應(yīng)拓展 Class 對(duì)象
- 通過反射進(jìn)行實(shí)例化
- 調(diào)用 injectExtension 方法向拓展實(shí)例中注入依賴
前兩個(gè)邏輯比較好理解募强,第三個(gè)邏輯用于向自適應(yīng)拓展對(duì)象中注入依賴。這里調(diào)用 injectExtension 方法的目的是為手工編碼的自適應(yīng)拓展注入依賴崇摄,這一點(diǎn)需要大家注意一下擎值。關(guān)于 injectExtension 方法,前文已經(jīng)分析過了逐抑,這里不再贅述鸠儿。接下來,分析 getAdaptiveExtensionClass
方法的邏輯厕氨。
/**
* 獲取自適應(yīng)擴(kuò)展的類
*/
private Class<?> getAdaptiveExtensionClass() {
// 通過 SPI 獲取所有的拓展類进每,在這一步判斷所有的所有的擴(kuò)展類上有沒有 Adaptive 注解,如果有命斧,將 cachedAdaptiveClass 賦值
getExtensionClasses();
// 判斷 cachedAdaptiveClass 是否有值田晚,如果有直接返回
if (cachedAdaptiveClass != null) {
return cachedAdaptiveClass;
}
// 創(chuàng)建自適應(yīng)拓展類
return cachedAdaptiveClass = createAdaptiveExtensionClass();
}
getAdaptiveExtensionClass
方法同樣包含了三個(gè)邏輯,如下:
- 調(diào)用
getExtensionClasses
獲取所有的拓展類 - 檢查緩存国葬,若緩存不為空贤徒,則返回緩存
- 若緩存為空遭京,則調(diào)用
createAdaptiveExtensionClass
創(chuàng)建自適應(yīng)拓展類
首先從第一個(gè)邏輯說起,getExtensionClasses 這個(gè)方法用于獲取某個(gè)接口的所有實(shí)現(xiàn)類泞莉。比如該方法可以獲取 Protocol 接口的 DubboProtocol哪雕、HttpProtocol、InjvmProtocol 等實(shí)現(xiàn)類鲫趁。在獲取實(shí)現(xiàn)類的過程中斯嚎,如果某個(gè)某個(gè)實(shí)現(xiàn)類被 Adaptive 注解修飾了,那么該類就會(huì)被賦值給 cachedAdaptiveClass 變量挨厚。如果所有的實(shí)現(xiàn)類均未被 Adaptive 注解修飾堡僻,那么執(zhí)行第三步邏輯,創(chuàng)建自適應(yīng)拓展類疫剃。相關(guān)代碼如下:
/**
* 創(chuàng)建自適應(yīng)拓展類
*/
private Class<?> createAdaptiveExtensionClass() {
// 構(gòu)建自適應(yīng)拓展代碼
String code = createAdaptiveExtensionClassCode();
// 獲取編譯器實(shí)現(xiàn)類
ClassLoader classLoader = findClassLoader();
org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
// 編譯代碼钉疫,生成 Class
return compiler.compile(code, classLoader);
}
createAdaptiveExtensionClass
方法用于生成自適應(yīng)拓展類,該方法首先會(huì)生成自適應(yīng)拓展類的源碼巢价,然后通過Compiler
實(shí)例(Dubbo 默認(rèn)使用 javassist
作為編譯器)編譯源碼牲阁,得到代理類 Class 實(shí)例。
- 接下來壤躲,我們把重點(diǎn)放在代理類代碼生成的邏輯上城菊,即
createAdaptiveExtensionClassCode
方法上。
- 在生成代理類源碼之前碉克,createAdaptiveExtensionClassCode 方法首先會(huì)通過反射檢測接口方法是否包含 Adaptive 注解凌唬。對(duì)于要生成自適應(yīng)拓展的接口,Dubbo 要求該接口至少有一個(gè)方法被 Adaptive 注解修飾漏麦。若不滿足此條件客税,就會(huì)拋出運(yùn)行時(shí)異常。相關(guān)代碼如下:
// type是擴(kuò)展點(diǎn)class
Method[] methods = type.getMethods();
boolean hasAdaptiveAnnotation = false;
for (Method m : methods) {
// 檢測方法上是否有 Adaptive 注解
if (m.isAnnotationPresent(Adaptive.class)) {
hasAdaptiveAnnotation = true;
break;
}
}
// no need to generate adaptive class since there's no adaptive method found.
if (!hasAdaptiveAnnotation) {
throw new IllegalStateException("No adaptive method exist on extension " + type.getName() + ", refuse to create the adaptive class!");
}
- 通過 Adaptive 注解檢測后撕贞,即可開始生成代碼更耻。代碼生成的順序與 Java 文件內(nèi)容順序一致,首先會(huì)生成 package 語句麻掸,然后生成 import 語句酥夭,緊接著生成類名等代碼。整個(gè)邏輯如下:
// 生成 package 代碼:package + type 所在包
codeBuilder.append("package ").append(type.getPackage().getName()).append(";");
// 生成 import 代碼:import + ExtensionLoader 全限定名
codeBuilder.append("\nimport ").append(ExtensionLoader.class.getName()).append(";");
// 生成類代碼:public class + type簡單名稱 + $Adaptive + implements + type全限定名 + {
codeBuilder.append("\npublic class ").append(type.getSimpleName()).append("$Adaptive").append(" implements ").append(type.getCanonicalName()).append(" {");
這里以上篇的AdaptiveExt
為例脊奋,生成的代碼如下:
package com.hui.wang.dubbo.learn.dubbo.adaptive;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
public class AdaptiveExt$Adaptive implements com.hui.wang.dubbo.learn.dubbo.adaptive.AdaptiveExt {
}
- 接著開始生成對(duì)應(yīng)的方法熬北,一個(gè)方法可以被
Adaptive
注解修飾,也可以不被修飾诚隙。這里將未被Adaptive
注解修飾的方法稱為“無Adaptive
注解方法”讶隐。Dubbo 不會(huì)為沒有標(biāo)注Adaptive
注解的方法生成代理邏輯,對(duì)于該種類型的方法久又,僅會(huì)生成一句拋出異常的代碼巫延。生成邏輯如下:
for (Method method : methods) {
// 省略無關(guān)邏輯
Adaptive adaptiveAnnotation = method.getAnnotation(Adaptive.class);
StringBuilder code = new StringBuilder(512);
// 如果方法上無 Adaptive 注解效五,則生成 throw new UnsupportedOperationException(...) 代碼
if (adaptiveAnnotation == null) {
// 生成的代碼格式如下:
// throw new UnsupportedOperationException(
// "method " + 方法簽名 + of interface + 全限定接口名 + is not adaptive method!”)
code.append("throw new UnsupportedOperationException(\"method ")
.append(method.toString()).append(" of interface ")
.append(type.getName()).append(" is not adaptive method!\");");
} else {
// 省略無關(guān)邏輯
}
// 省略無關(guān)邏輯
}
以 dubbo 中的 com.alibaba.dubbo.rpc.Protocol
接口的destroy
方法為例,上面代碼生成的內(nèi)容如下:
public void destroy() {
throw new UnsupportedOperationException(
"method public abstract void com.alibaba.dubbo.rpc.Protocol.destroy() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
}
前面說過方法代理邏輯會(huì)從 URL 中提取目標(biāo)拓展的名稱炉峰,因此代碼生成邏輯的一個(gè)重要的任務(wù)是從方法的參數(shù)列表或者其他參數(shù)中獲取 URL 數(shù)據(jù)畏妖。舉例說明一下,我們要為 com.alibaba.dubbo.rpc.Protocol
接口的refer
和 export
方法生成代理邏輯疼阔。refer
和 export
方法方法簽名如下:
<T> Exporter<T> export(Invoker<T> invoker) throws RpcException;
<T> Invoker<T> refer(Class<T> aClass, URL url) throws RpcException;
對(duì)于 refer 方法戒劫,通過遍歷 refer 的參數(shù)列表即可獲取 URL 數(shù)據(jù),這個(gè)還比較簡單婆廊。對(duì)于 export 方法迅细,獲取 URL 數(shù)據(jù)則要麻煩一些。export 參數(shù)列表中沒有 URL 參數(shù)淘邻,因此需要從 Invoker 參數(shù)中獲取 URL 數(shù)據(jù)茵典。獲取方式是調(diào)用 Invoker 中可返回 URL 的 getter 方法,比如 getUrl宾舅。如果 Invoker 中無相關(guān) getter 方法统阿,此時(shí)則會(huì)拋出異常。整個(gè)邏輯如下:
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) {
// ${無 Adaptive 注解方法代碼生成邏輯}
} else {
int urlTypeIndex = -1;
// 遍歷參數(shù)列表贴浙,確定 URL 參數(shù)位置
for (int i = 0; i < pts.length; ++i) {
if (pts[i].equals(URL.class)) {
urlTypeIndex = i;
break;
}
}
// urlTypeIndex != -1砂吞,表示參數(shù)列表中存在 URL 參數(shù)
if (urlTypeIndex != -1) {
// 為 URL 類型參數(shù)生成判空代碼,格式如下:
// if (arg + urlTypeIndex == null)
// throw new IllegalArgumentException("url == null");
String s = String.format("\nif (arg%d == null) throw new IllegalArgumentException(\"url == null\");",
urlTypeIndex);
code.append(s);
// 為 URL 類型參數(shù)生成賦值代碼弓熏,形如 URL url = arg1
s = String.format("\n%s url = arg%d;", URL.class.getName(), urlTypeIndex);
code.append(s);
// 參數(shù)列表中不存在 URL 類型參數(shù)
} else {
String attribMethod = null;
LBL_PTS:
// 遍歷方法的參數(shù)類型列表
for (int i = 0; i < pts.length; ++i) {
// 獲取某一類型參數(shù)的全部方法
Method[] ms = pts[i].getMethods();
// 遍歷方法列表挟冠,尋找可返回 URL 的 getter 方法
for (Method m : ms) {
String name = m.getName();
// 1. 方法名以 get 開頭,或方法名大于3個(gè)字符
// 2. 方法的訪問權(quán)限為 public
// 3. 非靜態(tài)方法
// 4. 方法參數(shù)數(shù)量為0
// 5. 方法返回值類型為 URL
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;
// 結(jié)束 for (int i = 0; i < pts.length; ++i) 循環(huán)
break LBL_PTS;
}
}
}
if (attribMethod == null) {
// 如果所有參數(shù)中均不包含可返回 URL 的 getter 方法簿姨,則拋出異常
throw new IllegalStateException("fail to create adaptive class for interface ...");
}
// 為可返回 URL 的參數(shù)生成判空代碼,格式如下:
// if (arg + urlTypeIndex == null)
// throw new IllegalArgumentException("參數(shù)全限定名 + argument == null");
String s = String.format("\nif (arg%d == null) throw new IllegalArgumentException(\"%s argument == null\");",
urlTypeIndex, pts[urlTypeIndex].getName());
code.append(s);
// 為 getter 方法返回的 URL 生成判空代碼膘怕,格式如下:
// if (argN.getter方法名() == null)
// throw new IllegalArgumentException(參數(shù)全限定名 + argument getUrl() == null);
s = String.format("\nif (arg%d.%s() == null) throw new IllegalArgumentException(\"%s argument %s() == null\");",
urlTypeIndex, attribMethod, pts[urlTypeIndex].getName(), attribMethod);
code.append(s);
// 生成賦值語句捞烟,格式如下:
// URL全限定名 url = argN.getter方法名(),比如
// com.alibaba.dubbo.common.URL url = invoker.getUrl();
s = String.format("%s url = arg%d.%s();", URL.class.getName(), urlTypeIndex, attribMethod);
code.append(s);
}
// 省略無關(guān)代碼
}
// 省略無關(guān)代碼
}
這段代碼主要目的是為了獲取 URL 數(shù)據(jù)王悍,并為之生成判空和賦值代碼破镰。以Protocol
的 refer 和 export 方法為例,上面的代碼為它們生成如下內(nèi)容(代碼已格式化):
refer
:
if (arg1 == null) {
throw new IllegalArgumentException("url == null");
}
com.alibaba.dubbo.common.URL url = arg1;
export
:
if (arg0 == null) {
throw new IllegalArgumentException(
"com.alibaba.dubbo.rpc.Invoker argument == null");
}
if (arg0.getUrl() == null) {
throw new IllegalArgumentException(
"com.alibaba.dubbo.rpc.Invoker argument getUrl() == null");
}
com.alibaba.dubbo.common.URL url = arg0.getUrl();
- 接下來獲取
Adaptive
注解值压储,Adaptive 注解值 value 類型為 String[]鲜漩,可填寫多個(gè)值,默認(rèn)情況下為空數(shù)組集惋。若 value 為非空數(shù)組孕似,直接獲取數(shù)組內(nèi)容即可。若 value 為空數(shù)組刮刑,則需進(jìn)行額外處理喉祭。處理過程是將類名轉(zhuǎn)換為字符數(shù)組养渴,然后遍歷字符數(shù)組,并將字符放入 StringBuilder 中泛烙。若字符為大寫字母理卑,則向 StringBuilder 中添加點(diǎn)號(hào),隨后將字符變?yōu)樾懘嫒?StringBuilder 中蔽氨。比如 LoadBalance 經(jīng)過處理后傻工,得到 load.balance。
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) {
// ${無 Adaptive 注解方法代碼生成邏輯}
} else {
// ${獲取 URL 數(shù)據(jù)}
String[] value = adaptiveAnnotation.value();
// value 為空數(shù)組
if (value.length == 0) {
// 獲取類名孵滞,并將類名轉(zhuǎn)換為字符數(shù)組
String splitName = StringUtils.camelToSplitName(type.getSimpleName(), ".");
value = new String[]{splitName};
}
// 省略無關(guān)代碼
}
// 省略無關(guān)邏輯
}
- 檢測 Invocation 參數(shù)中捆,此段邏輯是檢測方法列表中是否存在 Invocation 類型的參數(shù),若存在坊饶,則為其生成判空代碼和其他一些代碼泄伪。相應(yīng)的邏輯如下:
for (Method method : methods) {
Class<?> rt = method.getReturnType();
Class<?>[] pts = method.getParameterTypes(); // 獲取參數(shù)類型列表
Class<?>[] ets = method.getExceptionTypes();
Adaptive adaptiveAnnotation = method.getAnnotation(Adaptive.class);
StringBuilder code = new StringBuilder(512);
if (adaptiveAnnotation == null) {
// ${無 Adaptive 注解方法代碼生成邏輯}
} else {
// ${獲取 URL 數(shù)據(jù)}
// ${獲取 Adaptive 注解值}
boolean hasInvocation = false;
// 遍歷參數(shù)類型列表
for (int i = 0; i < pts.length; ++i) {
// 判斷當(dāng)前參數(shù)名稱是否等于 com.alibaba.dubbo.rpc.Invocation
if (pts[i].getName().equals("com.alibaba.dubbo.rpc.Invocation")) {
// 為 Invocation 類型參數(shù)生成判空代碼
String s = String.format("\nif (arg%d == null) throw new IllegalArgumentException(\"invocation == null\");", i);
code.append(s);
// 生成 getMethodName 方法調(diào)用代碼,格式為:
// String methodName = argN.getMethodName();
s = String.format("\nString methodName = arg%d.getMethodName();", i);
code.append(s);
// 設(shè)置 hasInvocation 為 true
hasInvocation = true;
break;
}
}
}
// 省略無關(guān)邏輯
}
生成的代碼示例為:
if (argN == null) {
throw new IllegalArgumentException("invocation == null");
}
String methodName = argN.getMethodName();
- 生成拓展名獲取邏輯匿级,下面代碼是根據(jù) SPI 和 Adaptive 注解值生成“獲取拓展名邏輯”蟋滴,同時(shí)生成邏輯也受 Invocation 類型參數(shù)影響,綜合因素導(dǎo)致本段邏輯相對(duì)復(fù)雜痘绎。
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) {
// $無 Adaptive 注解方法代碼生成邏輯}
} else {
// ${獲取 URL 數(shù)據(jù)}
// ${獲取 Adaptive 注解值}
// ${檢測 Invocation 參數(shù)}
// 設(shè)置默認(rèn)拓展名津函,cachedDefaultName 源于 SPI 注解值,默認(rèn)情況下孤页,
// SPI 注解值為空串尔苦,此時(shí) cachedDefaultName = null
String defaultExtName = cachedDefaultName;
String getNameCode = null;
// 遍歷 value,這里的 value 是 Adaptive 的注解值行施,2.2.3.3 節(jié)分析過 value 變量的獲取過程允坚。
// 此處循環(huán)目的是生成從 URL 中獲取拓展名的代碼,生成的代碼會(huì)賦值給 getNameCode 變量蛾号。注意這
// 個(gè)循環(huán)的遍歷順序是由后向前遍歷的稠项。
for (int i = value.length - 1; i >= 0; --i) {
// 當(dāng) i 為最后一個(gè)元素的坐標(biāo)時(shí)
if (i == value.length - 1) {
// 默認(rèn)拓展名非空
if (null != defaultExtName) {
// protocol 是 url 的一部分,可通過 getProtocol 方法獲取鲜结,其他的則是從
// URL 參數(shù)中獲取展运。因?yàn)楂@取方式不同,所以這里要判斷 value[i] 是否為 protocol
if (!"protocol".equals(value[i]))
// hasInvocation 用于標(biāo)識(shí)方法參數(shù)列表中是否有 Invocation 類型參數(shù)
if (hasInvocation)
// 生成的代碼功能等價(jià)于下面的代碼:
// url.getMethodParameter(methodName, value[i], defaultExtName)
// 以 LoadBalance 接口的 select 方法為例精刷,最終生成的代碼如下:
// url.getMethodParameter(methodName, "loadbalance", "random")
getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName);
else
// 生成的代碼功能等價(jià)于下面的代碼:
// url.getParameter(value[i], defaultExtName)
getNameCode = String.format("url.getParameter(\"%s\", \"%s\")", value[i], defaultExtName);
else
// 生成的代碼功能等價(jià)于下面的代碼:
// ( url.getProtocol() == null ? defaultExtName : url.getProtocol() )
getNameCode = String.format("( url.getProtocol() == null ? \"%s\" : url.getProtocol() )", defaultExtName);
// 默認(rèn)拓展名為空
} else {
if (!"protocol".equals(value[i]))
if (hasInvocation)
// 生成代碼格式同上
getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName);
else
// 生成的代碼功能等價(jià)于下面的代碼:
// url.getParameter(value[i])
getNameCode = String.format("url.getParameter(\"%s\")", value[i]);
else
// 生成從 url 中獲取協(xié)議的代碼拗胜,比如 "dubbo"
getNameCode = "url.getProtocol()";
}
} else {
if (!"protocol".equals(value[i]))
if (hasInvocation)
// 生成代碼格式同上
getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName);
else
// 生成的代碼功能等價(jià)于下面的代碼:
// url.getParameter(value[i], getNameCode)
// 以 Transporter 接口的 connect 方法為例,最終生成的代碼如下:
// url.getParameter("client", url.getParameter("transporter", "netty"))
getNameCode = String.format("url.getParameter(\"%s\", %s)", value[i], getNameCode);
else
// 生成的代碼功能等價(jià)于下面的代碼:
// url.getProtocol() == null ? getNameCode : url.getProtocol()
// 以 Protocol 接口的 connect 方法為例贬养,最終生成的代碼如下:
// url.getProtocol() == null ? "dubbo" : url.getProtocol()
getNameCode = String.format("url.getProtocol() == null ? (%s) : url.getProtocol()", getNameCode);
}
}
// 生成 extName 賦值代碼
code.append("\nString extName = ").append(getNameCode).append(";");
// 生成 extName 判空代碼
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);
}
// 省略無關(guān)邏輯
}
上面代碼比較復(fù)雜挤土,不是很好理解。對(duì)于這段代碼误算,建議大家寫點(diǎn)測試用例仰美,對(duì) Protocol迷殿、LoadBalance 以及 Transporter 等接口的自適應(yīng)拓展類代碼生成過程進(jìn)行調(diào)試庆寺。這里我以 Transporter 接口的自適應(yīng)拓展類代碼生成過程舉例說明。首先看一下 Transporter 接口的定義,如下:
@SPI("netty")
public interface Transporter {
// @Adaptive({server, transporter})
@Adaptive({Constants.SERVER_KEY, Constants.TRANSPORTER_KEY})
Server bind(URL url, ChannelHandler handler) throws RemotingException;
// @Adaptive({client, transporter})
@Adaptive({Constants.CLIENT_KEY, Constants.TRANSPORTER_KEY})
Client connect(URL url, ChannelHandler handler) throws RemotingException;
}
下面對(duì) connect 方法代理邏輯生成的過程進(jìn)行分析,此時(shí)生成代理邏輯所用到的變量如下:
String defaultExtName = "netty";
boolean hasInvocation = false;
String getNameCode = null;
String[] value = ["client", "transporter"];
根據(jù)上面的代碼邏輯,對(duì) value 數(shù)組進(jìn)行遍歷剩檀,此時(shí) i = 1, value[i] = "transporter",生成的代碼如下:
getNameCode = url.getParameter("transporter", "netty");
接下來字币,for 循環(huán)繼續(xù)執(zhí)行士复,此時(shí) i = 0, value[i] = "client",生成的代碼如下:
getNameCode = url.getParameter("client", url.getParameter("transporter", "netty"));
for 循環(huán)結(jié)束運(yùn)行便贵,現(xiàn)在為 extName 變量生成賦值和判空代碼冗荸,如下:
String extName = url.getParameter("client", url.getParameter("transporter", "netty"));
if (extName == null) {
throw new IllegalStateException(
"Fail to get extension(com.alibaba.dubbo.remoting.Transporter) name from url(" + url.toString()
+ ") use keys([client, transporter])");
}
- 生成拓展加載與目標(biāo)方法調(diào)用邏輯蚌本,下面代碼邏輯用于根據(jù)拓展名加載拓展實(shí)例轴猎,并調(diào)用拓展實(shí)例的目標(biāo)方法捻脖。
// 生成拓展獲取代碼,格式如下:
// type全限定名 extension = (type全限定名)ExtensionLoader全限定名
// .getExtensionLoader(type全限定名.class).getExtension(extName);
// Tips: 格式化字符串中的 %<s 表示使用前一個(gè)轉(zhuǎn)換符所描述的參數(shù)矛渴,即 type 全限定名
s = String.format("\n%s extension = (%<s)%s.getExtensionLoader(%s.class).getExtension(extName);",
type.getName(), ExtensionLoader.class.getSimpleName(), type.getName());
code.append(s);
// 如果方法返回值類型非 void晶府,則生成 return 語句剂习。
if (!rt.equals(void.class)) {
code.append("\nreturn ");
}
// 生成目標(biāo)方法調(diào)用邏輯鳞绕,格式為:
// extension.方法名(arg0, arg2, ..., argN);
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(");");
}
以 Protocol 接口舉例說明,上面代碼生成的內(nèi)容如下:
com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader
.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
return extension.refer(arg0, arg1);
- 生成完整的方法茬射,下面代碼生成的收尾工作钟病,主要用于生成方法定義的代碼票唆。相關(guān)邏輯如下:
// public + 返回值全限定名 + 方法名 + (
codeBuilder.append("\npublic ")
.append(rt.getCanonicalName())
.append(" ")
.append(method.getName())
.append("(");
// 添加參數(shù)列表代碼
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}");
以 Protocol 的 refer 方法為例缘回,上面代碼生成的內(nèi)容如下:
public com.alibaba.dubbo.rpc.Invoker refer(java.lang.Class arg0, com.alibaba.dubbo.common.URL arg1) {
// 方法體
}
- 關(guān)于自適應(yīng)拓展的原理啦吧,實(shí)現(xiàn)就分析完了肆糕』此ぃ總的來說自適應(yīng)拓展整個(gè)邏輯還是很復(fù)雜的造垛,并不是很容易弄懂办斑。因此髓迎,大家在閱讀該部分源碼時(shí),耐心一些。同時(shí)多進(jìn)行調(diào)試凛忿,也可以通過生成好的代碼思考代碼的生成邏輯委乌。
下面貼出上篇中的AdaptiveExt
接口生成的自適應(yīng)類的代碼:
package com.hui.wang.dubbo.learn.dubbo.adaptive;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
public class AdaptiveExt$Adaptive implements com.hui.wang.dubbo.learn.dubbo.adaptive.AdaptiveExt {
public java.lang.String echo(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("myAdaptiveName");
if (extName == null) {
throw new IllegalStateException(
"Fail to get extension(com.hui.wang.dubbo.learn.dubbo.adaptive.AdaptiveExt) name from url(" +
url.toString() + ") use keys([myAdaptiveName])");
}
com.hui.wang.dubbo.learn.dubbo.adaptive.AdaptiveExt extension = (com.hui.wang.dubbo.learn.dubbo.adaptive.AdaptiveExt) ExtensionLoader.getExtensionLoader(com.hui.wang.dubbo.learn.dubbo.adaptive.AdaptiveExt.class)
return extension.echo(arg0, arg1);
}
}
看到這個(gè)生成的代碼是不是和上篇寫的AdaptiveExtProxy
大致差不多著蛙。
參考示例
- 編寫一個(gè)測試樣例效拭,代碼如下挤渔,用于生成
com.alibaba.dubbo.remoting.Transporter
和com.alibaba.dubbo.rpc.Protocol
自適應(yīng)類。
@Test
public void testAdaptive() throws Exception{
ExtensionLoader<Transporter> transporterExtensionLoader = ExtensionLoader.getExtensionLoader(Transporter.class);
transporterExtensionLoader.getAdaptiveExtension();
ExtensionLoader<Protocol> extensionLoader = ExtensionLoader.getExtensionLoader(Protocol.class);
extensionLoader.getAdaptiveExtension();
}
- 這里給大家推薦一個(gè)阿里的開源工具擂红,Arthas,直接將運(yùn)行的類反編譯成Java文件赎婚。
下面我貼出com.alibaba.dubbo.remoting.Transporter
和com.alibaba.dubbo.rpc.Protocol
自適應(yīng)類的代碼:
public class Protocol$Adaptive implements Protocol {
@Override
public void destroy() {
throw new UnsupportedOperationException("method public abstract void com.alibaba.dubbo.rpc.Protocol.destroy() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
}
@Override
public int getDefaultPort() {
throw new UnsupportedOperationException("method public abstract int com.alibaba.dubbo.rpc.Protocol.getDefaultPort() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
}
public Invoker refer(Class class_, URL uRL) throws RpcException {
String string;
if (uRL == null) {
throw new IllegalArgumentException("url == null");
}
URL uRL2 = uRL;
String string2 = string = uRL2.getProtocol() == null ? "dubbo" : uRL2.getProtocol();
if (string == null) {
throw new IllegalStateException(new StringBuffer().append("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(").append(uRL2.toString()).append(") use keys([protocol])").toString());
}
Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(string);
return protocol.refer(class_, uRL);
}
public Exporter export(Invoker invoker) throws RpcException {
String string;
if (invoker == null) {
throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null");
}
if (invoker.getUrl() == null) {
throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument getUrl() == null");
}
URL uRL = invoker.getUrl();
String string2 = string = uRL.getProtocol() == null ? "dubbo" : uRL.getProtocol();
if (string == null) {
throw new IllegalStateException(new StringBuffer().append("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(").append(uRL.toString()).append(") use keys([protocol])").toString());
}
Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(string);
return protocol.export(invoker);
}
}
public class Transporter$Adaptive implements Transporter {
@Override
public Client connect(URL uRL, ChannelHandler channelHandler) throws RemotingException {
if (uRL == null) {
throw new IllegalArgumentException("url == null");
}
URL uRL2 = uRL;
String string = uRL2.getParameter("client", uRL2.getParameter("transporter", "netty"));
if (string == null) {
throw new IllegalStateException(new StringBuffer().append("Fail to get extension(com.alibaba.dubbo.remoting.Transporter) name from url(").append(uRL2.toString()).append(") use keys([client, transporter])").toString());
}
Transporter transporter = ExtensionLoader.getExtensionLoader(Transporter.class).getExtension(string);
return transporter.connect(uRL, channelHandler);
}
@Override
public Server bind(URL uRL, ChannelHandler channelHandler) throws RemotingException {
if (uRL == null) {
throw new IllegalArgumentException("url == null");
}
URL uRL2 = uRL;
String string = uRL2.getParameter("server", uRL2.getParameter("transporter", "netty"));
if (string == null) {
throw new IllegalStateException(new StringBuffer().append("Fail to get extension(com.alibaba.dubbo.remoting.Transporter) name from url(").append(uRL2.toString()).append(") use keys([server, transporter])").toString());
}
Transporter transporter = ExtensionLoader.getExtensionLoader(Transporter.class).getExtension(string);
return transporter.bind(uRL, channelHandler);
}
}