dubbo SPI
dubbo自適應擴展機制
自適應拓展類加載過程
- 加載入口方法
- 加載@SPI接口所有的實現類,如果有實現類有@Adaptive註解,則直接返回此類。
- 判斷@SPI接口中的方法是否至少有一個方法具有@Adaptive註解,如果沒有,則拋異常。
- 通過反射獲取接口的方法數組,然後遍歷數組生成代理類內部代碼。
- 對於沒有@Adaptive註解的方法,生成的代理方法內部直接拋出異常。
- 代理方法體核心邏輯就是要獲取代理類的在配置文件中對應的名稱,即extName,然後加載對應的實現類。
- 獲取URL參數
以Protocol爲例
- 獲取 Adaptive 註解值,註解值是一個String數組 values,如果註解沒有值則把SPI接口名的轉成小寫字母放入values。根據這些值從URL中獲取的值就是extName。
-
檢測 Invocation 參數
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 數據}
// ${獲取 Adaptive 註解值}
boolean hasInvocation = false;
// 遍歷參數類型列表
for (int i = 0; i < pts.length; ++i) {
// 判斷當前參數名稱是否等於 com.alibaba.dubbo.rpc.Invocation
if (pts[i].getName().equals("com.alibaba.dubbo.rpc.Invocation")) {
// 爲 Invocation 類型參數生成判空代碼
String s = String.format("\nif (arg%d == null) throw new IllegalArgumentException(\"invocation == null\");", i);
code.append(s);
// 生成 getMethodName 方法調用代碼,格式爲:
// String methodName = argN.getMethodName();
s = String.format("\nString methodName = arg%d.getMethodName();", i);
code.append(s);
// 設置 hasInvocation 爲 true
hasInvocation = true;
break;
}
}
}
// 省略無關邏輯
}
- extName獲取邏輯,從後至前遍歷values,從URL中獲取對應的值,最終values數組中第一個值對應URL中的值就是最後的extName。
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 數據}
// ${獲取 Adaptive 註解值}
// ${檢測 Invocation 參數}
// 設置默認拓展名,cachedDefaultName 源於 SPI 註解值,默認情況下,
// SPI 註解值爲空串,此時 cachedDefaultName = null
String defaultExtName = cachedDefaultName;
String getNameCode = null;
// 遍歷 value,這裏的 value 是 Adaptive 的註解值,2.2.3.3 節分析過 value 變量的獲取過程。
// 此處循環目的是生成從 URL 中獲取拓展名的代碼,生成的代碼會賦值給 getNameCode 變量。注意這
// 個循環的遍歷順序是由後向前遍歷的。
for (int i = value.length - 1; i >= 0; --i) {
// 當 i 爲最後一個元素的座標時
if (i == value.length - 1) {
// 默認拓展名非空
if (null != defaultExtName) {
// protocol 是 url 的一部分,可通過 getProtocol 方法獲取,其他的則是從
// URL 參數中獲取。因爲獲取方式不同,所以這裏要判斷 value[i] 是否爲 protocol
if (!"protocol".equals(value[i]))
// hasInvocation 用於標識方法參數列表中是否有 Invocation 類型參數
if (hasInvocation)
// 生成的代碼功能等價於下面的代碼:
// 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
// 生成的代碼功能等價於下面的代碼:
// url.getParameter(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 {
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 中獲取協議的代碼,比如 "dubbo"
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 賦值代碼
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);
}
// 省略無關邏輯
}
- 使用extName加載拓展