一、前沿
調試 dubbo 源碼時,相信大家經常會遇到這樣一個問題,即調用 Protocol、Cluster、ProxyFactory、LoadBalance(這些類中都有 SPI 注解) 中定義的帶有 Adaptive 注解的方法 時總是按照以下流程調用:
步驟一: 調用 url 的 getXXX 方法擷取參數值
步驟二: 調用 ExtensionLoader 的 getExtensionLoader 擷取加載器
步驟三: 調用 ExtensionLoader 的 getExtension 根據從url擷取的參數作為類名稱加載實作類
如下圖所示:
debug 調試圖中可以,三個步驟的調用有一個共同點,就是具體的調用方法都是從 Protocol$Adaptive 這個類中調用的,這樣的類是随着定義類的執行個體化時加載的,如下圖:
ReferenceConfig 執行個體化時,Protocol、Cluster、ProxyFactory 分别會生成 Protocol$Adaptive、Cluster$Adaptive、ProxyFactory $Adaptive 代理類
下面開始介紹 dubbo 中的自适應拓展機制
二、自适應拓展機制原理
我們知道在 Dubbo 中,很多拓展都是通過 SPI 機制 進行加載的,比如 Protocol、Cluster、LoadBalance、ProxyFactory 等。有時,有些拓展并不想在架構啟動階段被加載,而是希望在拓展方法被調用時,根據運作時參數進行加載,即根據參數動态加載實作類。這聽起來有些沖突。拓展未被加載,那麼拓展方法就無法被調用(靜态方法除外)。拓展方法未被調用,拓展就無法被加載的。對于這個沖突的問題,Dubbo 通過自适應拓展機制很好的解決了
原理如下:
1)、Dubbo 會為拓展接口生成具有代理功能的代碼
2)、通過 javassist 或 jdk 編譯這段代碼,得到 Class 類
3)、通過反射建立代理類
自适應拓展機制的實作邏輯比較複雜,為了讓大家對自适應拓展有一個感性的認識,下面我們借用官網的一個示例進行示範。這是一個與汽車相關的例子,我們有一個車輪制造廠接口 WheelMaker:
public interface WheelMaker {
Wheel makeWheel(URL url);
}
// WheelMaker 接口的自适應實作類
public class AdaptiveWheelMaker implements WheelMaker {
public Wheel makeWheel(URL url) {
if (url == null) {
throw new IllegalArgumentException("url == null");
}
// 1.從 URL 中擷取 WheelMaker 名稱
String wheelMakerName = url.getParameter("Wheel.maker");
if (wheelMakerName == null) {
throw new IllegalArgumentException("wheelMakerName == null");
}
// 2.通過 SPI 加載具體的 WheelMaker
WheelMaker wheelMaker = ExtensionLoader
.getExtensionLoader(WheelMaker.class).getExtension(wheelMakerName);
// 3.調用目标方法
return wheelMaker.makeWheel(URL url);
}
}
AdaptiveWheelMaker 是一個代理類,類似 Protocol$Adaptive,與傳統的代理邏輯不同,AdaptiveWheelMaker 所代理的對象是在 makeWheel 方法中通過 SPI 加載得到的。makeWheel 方法主要做了三件事情:
1)、從 URL 中擷取 WheelMaker 名稱
2)、通過 SPI 加載具體的 WheelMaker 實作類
3)、調用實作類的目标方法
接下來,我們來看看汽車制造廠 CarMaker 接口與其實作類,代碼如下:
public interface CarMaker {
Car makeCar(URL url);
}
public class RaceCarMaker implements CarMaker {
WheelMaker wheelMaker;
// 通過 setter 注入 AdaptiveWheelMaker
public setWheelMaker(WheelMaker wheelMaker) {
this.wheelMaker = wheelMaker;
}
public Car makeCar(URL url) {
Wheel wheel = wheelMaker.makeWheel(url);
return new RaceCar(wheel, ...);
}
}
RaceCarMaker 持有一個 WheelMaker 類型的成員變量,在程式啟動時,我們可以将 AdaptiveWheelMaker 通過 setter 方法注入到 RaceCarMaker 中。在運作時,假設有這樣一個 url 參數傳入:
dubbo://192.168.0.101:20880/XxxService?wheel.maker=MichelinWheelMaker
RaceCarMaker 的 makeCar 方法将上面的 url 作為參數傳給 AdaptiveWheelMaker 的 makeWheel 方法,makeWheel 方法從 url 中提取 wheel.maker 參數,得到 MichelinWheelMaker。之後再通過 SPI 加載配置名為 MichelinWheelMaker 的實作類,得到具體的 WheelMaker 執行個體。
上面的示例展示了自适應拓展類的核心實作 ---- 在拓展接口的方法被調用時,通過 SPI 加載具體的拓展實作類,并調用拓展對象的同名方法。
接下來,我們深入到源碼中,探究自适應拓展類生成的過程
三、源碼
在了解自适應拓展源碼之前,我們必須先了解一個自定義注解 Adaptive,隻有帶有 Adaptive 注解的方法或者類才能使用自适應拓展機制,自定義注解 Adaptive 源碼如下:
/**
* Provide helpful information for {@link ExtensionLoader} to inject dependency extension instance.
*
* @see ExtensionLoader
* @see URL
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Adaptive {
/**
* Decide which target extension to be injected. The name of the target extension is decided by the parameter passed
* in the URL, and the parameter names are given by this method.
* <p>
* If the specified parameters are not found from {@link URL}, then the default extension will be used for
* dependency injection (specified in its interface's {@link SPI}).
* <p>
* For example, given <code>String[] {"key1", "key2"}</code>:
* <ol>
* <li>find parameter 'key1' in URL, use its value as the extension's name</li>
* <li>try 'key2' for extension's name if 'key1' is not found (or its value is empty) in URL</li>
* <li>use default extension if 'key2' doesn't exist either</li>
* <li>otherwise, throw {@link IllegalStateException}</li>
* </ol>
* If the parameter names are empty, then a default parameter name is generated from interface's
* class name with the rule: divide classname from capital char into several parts, and separate the parts with
* dot '.', for example, for {@code org.apache.dubbo.xxx.YyyInvokerWrapper}, the generated name is
* <code>String[] {"yyy.invoker.wrapper"}</code>.
*
* @return parameter names in URL
*/
String[] value() default {};
}
從源碼中得知 Adaptive 可注解在類或方法上,Adaptive 注解在類上或者方法上有不同的實作邏輯,如下:
1)、Adaptive 注解在類上時,Dubbo 不會為該類生成代理類,Adaptive 注解在類上的情況很少,在 Dubbo 中,僅有兩個類被 Adaptive 注解了,分别是 AdaptiveCompiler 和 AdaptiveExtensionFactory,表示拓展的加載邏輯由人工編碼完成
2)、Adaptive 注解在方法上時,Dubbo 則會為該方法生成代理邏輯,表示拓展的加載邏輯需由架構自動生成
下面正式開始分析自适應拓展源碼,探究 XXX$Adaptive 代理類的生成過程,下面分為兩個部分分析:3.1 擷取自适應拓展 3.2 自适應拓展類生成代碼
3.1 擷取自适應拓展
在前言中可知,擷取自适應拓展是從 ExtensionLoader 的 getAdaptiveExtension 方法開始的,源碼如下:
// 1、ExtensionLoader 的 getAdaptiveExtension 方法
@SuppressWarnings("unchecked")
public T getAdaptiveExtension() {
// 先從緩存中擷取自适應拓展執行個體
Object instance = cachedAdaptiveInstance.get();
if (instance == null) {
if (createAdaptiveInstanceError == null) {
// 雙重校驗鎖方式擷取自适應拓展執行個體
synchronized (cachedAdaptiveInstance) {
instance = cachedAdaptiveInstance.get();
if (instance == null) {
try {
// 調用 ExtensionLoader 的 createAdaptiveExtension 方法建立自适應拓展執行個體
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;
}
// 2、ExtensionLoader 的 createAdaptiveExtension 方法
@SuppressWarnings("unchecked")
private T createAdaptiveExtension() {
try {
// 先調用 ExtensionLoader 的 getAdaptiveExtensionClass 方法擷取自适應拓展類,然後通過反射執行個體化,最後向拓展執行個體中注入依賴
return injectExtension((T) getAdaptiveExtensionClass().newInstance());
} catch (Exception e) {
throw new IllegalStateException("Can't create adaptive extension " + type + ", cause: " + e.getMessage(), e);
}
}
// 3、ExtensionLoader 的 getAdaptiveExtensionClass 方法
private Class<?> getAdaptiveExtensionClass() {
// 通過 SPI 擷取所有的拓展類
getExtensionClasses();
if (cachedAdaptiveClass != null) {
// 若緩存中有拓展類執行個體,則直接傳回緩存
return cachedAdaptiveClass;
}
// 調用 ExtensionLoader 的 createAdaptiveExtensionClass 方法建立自适應拓展類
return cachedAdaptiveClass = createAdaptiveExtensionClass();
}
// 4、ExtensionLoader 的 createAdaptiveExtensionClass 方法
private Class<?> createAdaptiveExtensionClass() {
// 建立自适應拓展類的代碼
String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();
// 擷取類加載器
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);
}
擷取自适應拓展過程并不是特别複雜,但是有以下問題需要注意:
1)、Dubbo 中有兩種類型的自适應拓展,一種是手工編碼的,一種是自動生成的。手工編碼的自适應拓展中可能存在着一些依賴,而自動生成的 Adaptive 拓展則不會依賴其他類。這裡調用 injectExtension 方法的目的是為手工編碼的自适應拓展注入依賴
2)、getExtensionClasses 在 SPI 機制 中我們已經分析過了,這裡不在贅述。通過 SPI 擷取所有的拓展類,比如該方法可以擷取 Protocol 接口的 DubboProtocol、HttpProtocol、InjvmProtocol 等實作類。在擷取實作類的過程中,如果某個實作類被 Adaptive 注解了,那麼該類就會被指派給 cachedAdaptiveClass 變量,如果所有的類都沒有被 Adaptive 注解,則建立自适應拓展類
3.2 自适應拓展類生成代碼
從 3.1 擷取自适應拓展 中可知建立自适應拓展類的代碼從 AdaptiveClassCodeGenerator 類的 generate 方法開始,源碼如下:
/**
* generate and return class code
*/
public String generate() {
// no need to generate adaptive class since there's no adaptive method found.
// 1、檢查接口方法 adaptive 注解
// 檢查接口的所有方法上是否有 adaptive 注解,如果所有方法都沒有 adaptive 注解的話,抛出異常
if (!hasAdaptiveMethod()) {
throw new IllegalStateException("No adaptive method exist on extension " + type.getName() + ", refuse to create the adaptive class!");
}
// 2、生成類代碼
StringBuilder code = new StringBuilder();
// 生成 package 代碼 ,例如:package + type 所在包名
code.append(generatePackageInfo());
// 生成 import 代碼 ,例如:import + ExtensionLoader 全限定名
code.append(generateImports());
// 生成 類 代碼 ,例如:public class + type簡單名稱 + $Adaptive implements + type全限定名 + {
code.append(generateClassDeclaration());
// 3、生成方法代碼
// 擷取接口的所有方法
Method[] methods = type.getMethods();
for (Method method : methods) {
code.append(generateMethod(method));
}
code.append("}");
if (logger.isDebugEnabled()) {
logger.debug(code.toString());
}
return code.toString();
}
建立自适應拓展類的代碼分為了三步:1、檢查接口方法的 Adaptive 注解,2、生成類代碼, 3、為接口所有方法生成方法代碼
1 和 2 的邏輯很簡單,這裡不在展開分析,我們看一下 2 中生成的類代碼以 Protocol 接口為例,如下:
package com.alibaba.dubbo.rpc;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
public class Protocol$Adaptive implements com.alibaba.dubbo.rpc.Protocol {
// 省略方法代碼
}
下面重點分析 3,即 生成方法代碼過程
3.2.1 生成方法代碼
上述代碼可知生成方法代碼邏輯在 AdaptiveClassCodeGenerator 的 generateMethod 方法中,源碼如下:
/**
* generate method declaration
*/
private String generateMethod(Method method) {
// 擷取方法傳回類型
String methodReturnType = method.getReturnType().getCanonicalName();
// 擷取方法名
String methodName = method.getName();
// 擷取方法體内容
String methodContent = generateMethodContent(method);
// 擷取方法參數清單,格式代碼:參數全限定名 arg0,參數全限定名 arg1,參數全限定名 arg2......
String methodArgs = generateMethodArguments(method);
// 擷取方法抛出的異常清單,格式代碼:throws Exception1,Exception2......
String methodThrows = generateMethodThrows(method);
// 生成完整方法代碼,代碼格式:
// public methodReturnType methodName(methodArgs) methodThrows { methodContent };
// 以 Protocol 的 refer 方法為例,代碼:
// public com.alibaba.dubbo.rpc.Invoker refer(java.lang.Class arg0, com.alibaba.dubbo.common.URL arg1) throws org.apache.dubbo.rpc.RpcException {
// // 方法體
//}
return String.format(CODE_METHOD_DECLARATION, methodReturnType, methodName, methodArgs, methodThrows, methodContent);
}
/**
* generate method content
*/
// 生成方法體内容
private String generateMethodContent(Method method) {
// 從方法上擷取 Adaptive 注解
Adaptive adaptiveAnnotation = method.getAnnotation(Adaptive.class);
StringBuilder code = new StringBuilder(512);
if (adaptiveAnnotation == null) {
// 無 Adaptive 注解的方法,則生成 throw new UnsupportedOperationException("The method +方法名+ of interface +全限定接口名+ is not adaptive method!"); 代碼
// 以 Protocol 的 destroy 方法為例,将會生成如下代碼:
// throw new UnsupportedOperationException("The method org.apache.dubbo.rpc.Protocol.destroy() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
return generateUnsupported(method);
} else {
// 确定 URL 參數位置,之是以做這個是因為動态加載拓展類的名稱參數都是從 URL 中擷取來的
int urlTypeIndex = getUrlTypeIndex(method);
// found parameter in URL type
// 存在 URL 參數
if (urlTypeIndex != -1) {
// Null Point check
// 為 URL 參數生成判空代碼,例如:
// if (arg + urlTypeIndex == null) throw new IllegalArgumentException("url == null"); org.apache.dubbo.common.URL url = arg + urlTypeIndex;
code.append(generateUrlNullCheck(urlTypeIndex));
} else {
// did not find parameter in URL type
// 不存在 URL 參數
// 從可以傳回 URL 對象的參數中擷取 URL生成代碼
code.append(generateUrlAssignmentIndirectly(method));
}
// 擷取 Adaptive 注解值
String[] value = getMethodAdaptiveValue(adaptiveAnnotation);
// 檢測方法參數中是否存在 Invocation 類型的參數
boolean hasInvocation = hasInvocationArgument(method);
// 為 Invocation 類型的參數生成判空代碼 和 生成 getMethodName 方法調用代碼
// if (argN == null) throw new IllegalArgumentException(\"invocation == null\"); String methodName = argN.getMethodName();\n
code.append(generateInvocationArgumentNullCheck(method));
// 根據 SPI 和 Adaptive 注解值生成拓展名代碼
code.append(generateExtNameAssignment(value, hasInvocation));
// check extName == null?
// 為 extName 生成判空代碼,如下:
// if(extName == null) throw new IllegalStateException(\"Failed to get extension +全限定接口名+ name from url (\" + url.toString() + \") use keys(Adaptive 注解值)\");\n
code.append(generateExtNameNullCheck(value));
// 生成拓展類加載代碼,格式代碼:type全限定名+ extension = ((type全限定名))ExtensionLoader全限定名.getExtensionLoader(type全限定名.class).getExtension(extName);
// 例如:org.apache.dubbo.rpc.cluster.Cluster extension = (org.apache.dubbo.rpc.cluster.Cluster)org.apache.dubbo.common.extension.ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.cluster.Cluster.class).getExtension(extName);
code.append(generateExtensionAssignment());
// return statement
// 生成 return 語句和調用目标方法邏輯代碼
code.append(generateReturnAndInvocation(method));
}
return code.toString();
}
/**
* get parameter with type <code>URL</code> from method parameter:
* <p>
* test if parameter has method which returns type <code>URL</code>
* <p>
* if not found, throws IllegalStateException
*/
// 從可以傳回 URL 對象的參數中擷取 URL生成代碼
private String generateUrlAssignmentIndirectly(Method method) {
// 擷取方法的所有參數
Class<?>[] pts = method.getParameterTypes();
// find URL getter method
for (int i = 0; i < pts.length; ++i) {
// 周遊參數的所有的方法,找出 getUrl 方法
for (Method m : pts[i].getMethods()) {
String name = m.getName();
// 1、方法名是 get 開頭的
// 2、方法名長度 > 3
// 3、方法是 public 類型
// 4、方法是非 static 類型
// 5、方法沒有參數
// 6、方法傳回值類型是 URL 類型
// 例如:Invoker 的 getUrl 方法 就符合要求
if ((name.startsWith("get") || name.length() > 3)
&& Modifier.isPublic(m.getModifiers())
&& !Modifier.isStatic(m.getModifiers())
&& m.getParameterTypes().length == 0
&& m.getReturnType() == URL.class) {
// 生成參數判空、參數的getUrl方法判空以及指派語句代碼
return generateGetUrlNullCheck(i, pts[i], name);
}
}
}
// getter method not found, throw
// getUrl 方法沒有找到的話,抛出異常
throw new IllegalStateException("Failed to create adaptive class for interface " + type.getName()
+ ": not found url parameter or url attribute in parameters of method " + method.getName());
}
/**
* 1, test if argi is null
* 2, test if argi.getXX() returns null
* 3, assign url with argi.getXX()
*/
// 生成參數判空、參數的getUrl方法判空以及指派語句代碼
private String generateGetUrlNullCheck(int index, Class<?> type, String method) {
// Null point check
StringBuilder code = new StringBuilder();
// 為可傳回 URL 的參數生成判空代碼,例如:
// if (argN == null) throw new IllegalArgumentException("參數全限定名 + argument == null");
code.append(String.format("if (arg%d == null) throw new IllegalArgumentException(\"%s argument == null\");\n",
index, type.getName()));
// 為參數的getUrl方法生成判空代碼,例如:
// if (argN.getUrl() == null) throw new IllegalArgumentException("參數全限定名 + argument + getUrl() == null");
code.append(String.format("if (arg%d.%s() == null) throw new IllegalArgumentException(\"%s argument %s() == null\");\n",
index, method, type.getName(), method));
// 生成指派語句,格式:URL全限定名 url = argN.getUrl()
// 例如: com.alibaba.dubbo.common.URL url = invoker.getUrl();
code.append(String.format("%s url = arg%d.%s();\n", URL.class.getName(), index, method));
return code.toString();
}
/**
* get value of adaptive annotation or if empty return splitted simple name
*/
// 擷取 Adaptive 注解值
private String[] getMethodAdaptiveValue(Adaptive adaptiveAnnotation) {
// 擷取 Adaptive 注解值數組
String[] value = adaptiveAnnotation.value();
// value is not set, use the value generated from class name as the key
// 數組為空是,使用類名生成注解值
if (value.length == 0) {
// 周遊類名字元,如果字元是大寫字母,字元轉成小寫字母,若該字元不是第一個字元的話,則先向StringBuilder中添加".",
// 然後将轉成的小寫字母添加到StringBuilder中,否則的話直接将字元添加到StringBuilder中
// 例如:LoadBalance 經過處理後,得到 load.balance
String splitName = StringUtils.camelToSplitName(type.getSimpleName(), ".");
value = new String[]{splitName};
}
return value;
}
/**
* generate extName assigment code
*/
// 根據 SPI 和 Adaptive 注解值生成拓展名代碼
private String generateExtNameAssignment(String[] value, boolean hasInvocation) {
// TODO: refactor it
String getNameCode = null;
// 周遊 Adaptive 的注解值,此處循環目的是生成從 URL 中擷取拓展名的代碼,生成的代碼會指派給 getNameCode 變量。
// 注意這個循環的周遊順序是由後向前周遊的
for (int i = value.length - 1; i >= 0; --i) {
if (i == value.length - 1) {
// defaultExtName 就是 SPI 注解值, SPI 注解值存在時
if (null != defaultExtName) {
// protocol 是 url 的一部分,可通過 getProtocol 方法擷取,其他的則是從
// URL 參數中擷取。因為擷取方式不同,是以這裡要判斷 value[i] 是否為 protocol
if (!"protocol".equals(value[i])) {
// 方法參數清單中是否有 Invocation 類型的參數
if (hasInvocation) {
// 有 Invocation 類型的參數,生成如下代碼:
// 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 {
// 無 Invocation 類型的參數,生成如下代碼:
// url.getParameter(methodName, value[i], defaultExtName)
getNameCode = String.format("url.getParameter(\"%s\", \"%s\")", value[i], defaultExtName);
}
} else {
// 生成如下代碼:
// ( url.getProtocol() == null ? defaultExtName : url.getProtocol() )
getNameCode = String.format("( url.getProtocol() == null ? \"%s\" : url.getProtocol() )", defaultExtName);
}
} else {
// SPI 注解值不存在時,即預設拓展名沒有
if (!"protocol".equals(value[i])) {
if (hasInvocation) {
// 生成代碼格式同上
getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName);
} else {
// 生成代碼:url.getParameter(value[i])
getNameCode = String.format("url.getParameter(\"%s\")", value[i]);
}
} else {
// 生成代碼:url.getProtocol()
getNameCode = "url.getProtocol()";
}
}
} else {
if (!"protocol".equals(value[i])) {
if (hasInvocation) {
// 生成代碼格式同上
getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName);
} else {
// 生成代碼: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 {
// 生成代碼: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 變量指派語句,代碼:String extName = getNameCode;
return String.format(CODE_EXT_NAME_ASSIGNMENT, getNameCode);
}
/**
* generate method invocation statement and return it if necessary
*/
// 生成 return 語句和調用目标方法邏輯代碼
private String generateReturnAndInvocation(Method method) {
// 建立傳回語句,void 類型是不用傳回
String returnStatement = method.getReturnType().equals(void.class) ? "" : "return ";
// 生成目标方法的參數清單,代碼:arg0,arg1,arg2......
String args = IntStream.range(0, method.getParameters().length)
.mapToObj(i -> String.format(CODE_EXTENSION_METHOD_INVOKE_ARGUMENT, i))
.collect(Collectors.joining(", "));
// 生成目标方法調用代碼:return extension.方法名(arg0,arg1,arg2......);
// 以 Protocol 的 refer 方法為例,代碼:return extension.refer(Class<T> type, URL url);
return returnStatement + String.format("extension.%s(%s);\n", method.getName(), args);
}
生成方法代碼的源碼比較多,邏輯也相對來說複雜一些,配合代碼中的注釋和例子,相信大家不難了解整個過程。
以 Protocol 接口為例,下面給大家展示一下生成的 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("The 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("The 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.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("Failed 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);
}
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("Failed 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);
}
}
結合生成的 Protocol$Adaptive 的源碼,單元測試debug一遍流程,你就可以完全了解上面生成拓展類代碼的源碼了
到這裡dubbo中的自适應拓展就講解完了,以調用 Protocol 的接口的 ref 方法為例,下面給大家看一下自适應拓展的整個過程:
1)、先通過 Protocol REF_PROTOCOL = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension(); 生成了 Protocol$Adaptive 代理類
2)、傳參 Url 為:dubbo://192.168.1.247:20887/org.apache.dubbo.config.spring.api.DemoService,調用 Protocol 的 refer 方法,此時直接調用是 Protocol$Adaptive 代理類的 refer 方法
3)、在 Protocol$Adaptive 的 ref 方法中先調用 url 中的 getProtocol() 方法擷取拓展類名稱,指派給 extName 變量
4)、然後調用 org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName); 語句擷取到具體的實作類的執行個體
5)、最後執行 extension.refer(arg0, arg1) 語句,調用 4 中擷取到的具體實作類的 ref 方法,最終傳回結果
四、總結
dubbo中的自适應拓展很好地實作了根據參數動态加載實作類,思想很贊,在閱讀源碼的時候,有些地方還是比較晦澀,希望大家多使用源碼的單元測試來debug調試,這樣子就對整個流程很清楚了。本文中有不正确的地方,請大家指正,謝謝
參考:
https://dubbo.apache.org/zh-cn/docs/source_code_guide/adaptive-extension.html