導(dǎo)讀
- 想要搞懂Dubbo底層實(shí)現(xiàn),ExtensionLoader是不可繞過得門檻,不能深刻理解其擴(kuò)展點(diǎn)設(shè)計(jì),源碼閱讀部分會(huì)很懵逼?。?!(當(dāng)然,即使擴(kuò)展點(diǎn)懂了,源碼也不一定能看懂,哈哈。玩笑話,意思就是Dubbo得源碼還是比較難讀的,因?yàn)橛泻芏喔拍钆c設(shè)計(jì)如果不弄清楚,基本上會(huì)被繞暈。)
- 關(guān)鍵字 :Dubbo 擴(kuò)展點(diǎn)設(shè)計(jì)(SPI)、ExtensionLoader
Dubbo 擴(kuò)展點(diǎn)定義
- 從 ExtensionLoader的源碼中,我們可以找到 Dubbo SPI加載的目錄有三個(gè):1. META-INF/services/ (標(biāo)準(zhǔn)的SPI路徑)2. META-INF/dubbo/ 3. META-INF/dubbo/internal/ (內(nèi)部實(shí)現(xiàn))
- 擴(kuò)展點(diǎn)文件定義格式:目錄 / 接口全路徑
- 文件內(nèi)容(常用格式)
registry=com.apache.dubbo.registry.integration.RegistryProtocol dubbo=com.apache.dubbo.rpc.protocol.dubbo.DubboProtocol filter=com.apache.dubbo.rpc.protocol.ProtocolFilterWrapper listener=com.apache.dubbo.rpc.protocol.ProtocolListenerWrapper # 沒有等號(hào)情況 com.apache.dubbo.registry.integration.XxxProtocol com.apache.dubbo.registry.integration.YyyProtocol # 等號(hào)前面有多個(gè)值得情況 zzz,default=com.apache.dubbo.registry.integration.CustomProtocol
以下解讀,作者根據(jù)具體的某個(gè)擴(kuò)展點(diǎn)去分析加載過程
Ⅰ. ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtion()
- 先判斷出當(dāng)前
type(Protocol.class)是否有擴(kuò)展類加載器,沒有就創(chuàng)建一個(gè)
- 先判斷出當(dāng)前
- 然后從緩存的
cachedAdaptiveInstance適配實(shí)例Holder中查找,沒有的話就創(chuàng)建一個(gè)適配類
ExtensionLoader#getExtensionLoader
- 然后從緩存的
- 查找適配類:加載當(dāng)前擴(kuò)展點(diǎn)對(duì)應(yīng)的全部擴(kuò)展實(shí)現(xiàn), META-INF/dubbo/ , META-INF/dubbo/internal/,META-INF/services/幾個(gè)路徑下,擴(kuò)展點(diǎn)上一定要有
@SPI注解,并且注解的值只能有一個(gè)(默認(rèn)擴(kuò)展點(diǎn))
- 查找適配類:加載當(dāng)前擴(kuò)展點(diǎn)對(duì)應(yīng)的全部擴(kuò)展實(shí)現(xiàn), META-INF/dubbo/ , META-INF/dubbo/internal/,META-INF/services/幾個(gè)路徑下,擴(kuò)展點(diǎn)上一定要有
- 如果擴(kuò)展實(shí)現(xiàn)上 帶有
@Adaptive注解,則當(dāng)作默認(rèn)的適配類擴(kuò)展(只能定義一個(gè)擴(kuò)展適配類實(shí)現(xiàn))
- 如果擴(kuò)展實(shí)現(xiàn)上 帶有
-
否則嘗試獲取包裝類擴(kuò)展(帶有擴(kuò)展點(diǎn)接口的構(gòu)造器),如果有則添加到
wrappers集合中。此處可以得知(適配類即使是包裝類的約束格式,也不會(huì)被當(dāng)作包裝類)
-
- 如果沒有包裝類,則直接獲取無參構(gòu)造器(此處目的是確保擴(kuò)展實(shí)現(xiàn)可以被實(shí)例化)
- 判斷
=號(hào)前面的擴(kuò)展實(shí)現(xiàn)名稱,若為空(等號(hào)前面沒有值),則取擴(kuò)展實(shí)現(xiàn)類除去后綴的部分并且全部小寫(例如:XxxProtocol 取 xxx,YyyProtocol 取 yyy)作為名稱name
- 判斷
- 再將
name根據(jù)逗號(hào)拆分,若擴(kuò)展實(shí)現(xiàn)類上有@Activate激活注解,則取name[0]作為key,注解作為value,放入cachedActivates緩存map中。然后邊遍歷name,依次添加到cachedNames(key = 擴(kuò)展實(shí)現(xiàn)class實(shí)例,value = name)中
- 再將
- 最后都放入到 當(dāng)前類型對(duì)應(yīng)得擴(kuò)展實(shí)現(xiàn)中
cachedClasses
- 最后都放入到 當(dāng)前類型對(duì)應(yīng)得擴(kuò)展實(shí)現(xiàn)中
- 如果緩存得適配類
cachedAdaptiveClass為空(表示 擴(kuò)展實(shí)現(xiàn)類上有標(biāo)注 @Adaptive),則需要?jiǎng)?chuàng)建適配類( Protocol 擴(kuò)展點(diǎn)沒有適配類 )
ExtensionLoader#getAdaptiveExtension
ExtensionLoader#createAdaptiveExtension
- 如果緩存得適配類
- 獲取擴(kuò)展點(diǎn)
Protocol所有方法,判斷方法上是否存在@Adaptive注解,若不存在則直接拋異常,否則生成動(dòng)態(tài)得適配類。如下:
- 獲取擴(kuò)展點(diǎn)
package com.apache.dubbo.rpc;
import com.apache.dubbo.common.extension.ExtensionLoader;
public class Protocol$Adaptive implements com.apache.dubbo.rpc.Protocol {
public void destroy()
{
throw new UnsupportedOperationException(
"method public abstract void com.apache.dubbo.rpc.Protocol.destroy() of interface com.apache.dubbo.rpc.Protocol is not adaptive method!"
);
}
public int getDefaultPort()
{
throw new UnsupportedOperationException(
"method public abstract int com.apache.dubbo.rpc.Protocol.getDefaultPort() of interface com.apache.dubbo.rpc.Protocol is not adaptive method!"
);
}
public com.apache.dubbo.rpc.Exporter export(com.apache.dubbo.rpc.Invoker arg0) throws com.apache.dubbo.rpc.RpcException
{
if(arg0 == null) throw new IllegalArgumentException("com.apache.dubbo.rpc.Invoker argument == null");
if(arg0.getUrl() == null) throw new IllegalArgumentException(
"com.apache.dubbo.rpc.Invoker argument getUrl() == null");
com.apache.dubbo.common.URL url = arg0.getUrl();
String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
if(extName == null) throw new IllegalStateException(
"Fail to get extension(com.apache.dubbo.rpc.Protocol) name from url(" + url.toString() +
") use keys([protocol])");
// 獲取url中指定得協(xié)議,默認(rèn)是 dubbo協(xié)議
com.apache.dubbo.rpc.Protocol extension = (com.apache.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(
com.apache.dubbo.rpc.Protocol.class).getExtension(extName);
return extension.export(arg0); // 調(diào)用真正得協(xié)議擴(kuò)展實(shí)現(xiàn)得export方法
}
public com.apache.dubbo.rpc.Invoker refer(java.lang.Class arg0, com.apache.dubbo.common.URL arg1) throws com.apache.dubbo.rpc.RpcException{
if(arg1 == null) throw new IllegalArgumentException("url == null");
com.apache.dubbo.common.URL url = arg1;
// 獲取url中指定得協(xié)議,默認(rèn)是 dubbo協(xié)議
String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
if(extName == null) throw new IllegalStateException(
"Fail to get extension(com.apache.dubbo.rpc.Protocol) name from url(" + url.toString() +
") use keys([protocol])");
// 獲取真實(shí)得協(xié)議擴(kuò)展實(shí)現(xiàn)
com.apache.dubbo.rpc.Protocol extension = (com.apache.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(
com.apache.dubbo.rpc.Protocol.class).getExtension(extName);
return extension.refer(arg0, arg1); // 調(diào)用真正得協(xié)議擴(kuò)展實(shí)現(xiàn)得refer方法
}
}
- 創(chuàng)建完適配類之后,接著再獲取
Compiler得適配類,用于動(dòng)態(tài)生成Protocol得適配類(其他動(dòng)態(tài)生成得適配類也是用Compiler來生成得)。由于Compiler存在適配類,即擴(kuò)展點(diǎn)類加載器對(duì)應(yīng)得緩存cachedAdaptiveClass不為空,直接返回。此處cachedAdaptiveClass = com.apache.dubbo.common.compiler.support.AdaptiveCompiler
- 創(chuàng)建完適配類之后,接著再獲取
- 實(shí)例化并調(diào)用
injectExtension方法注入其他擴(kuò)展實(shí)例(不適合框架實(shí)現(xiàn)得擴(kuò)展適配類,因?yàn)閯?dòng)態(tài)生成得適配類基本上不會(huì)依賴其他擴(kuò)展點(diǎn)):判斷生成得適配類中 是否存在類似方法:1. set開頭得 2. 只有一個(gè)參數(shù)類型 3. public訪問修飾符,若有,則截取set之后得首字母小姐得名稱將其作為擴(kuò)展名,然后通過ExtensionFactory工廠獲取此擴(kuò)展名得實(shí)例,調(diào)用當(dāng)前setXXX方法,叫之為IOC。由于AdaptiveCompiler只有一個(gè)set方法但是沒有對(duì)應(yīng)得 String.class類型得defaultCompiler擴(kuò)展點(diǎn),所以此處不會(huì)進(jìn)行注入:
- 實(shí)例化并調(diào)用
public static void setDefaultCompiler(String compiler) {
DEFAULT_COMPILER = compiler;
}

ExtensionLoader#injectExtension
- 接著調(diào)用適配擴(kuò)展實(shí)現(xiàn)
AdaptiveCompiler得compile方法,方法內(nèi)會(huì)獲取默認(rèn)得擴(kuò)展實(shí)現(xiàn)(此處是javassist擴(kuò)展名)JavassistCompiler,然后調(diào)用其compile方法動(dòng)態(tài)生成適配class實(shí)例。
- 接著調(diào)用適配擴(kuò)展實(shí)現(xiàn)
Ⅱ. ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(extName)
上面生成得適配類中,有一步是獲取真正調(diào)用擴(kuò)展實(shí)現(xiàn) :
com.apache.dubbo.rpc.Protocol extension = (com.apache.dubbo.rpc.Protocol)
ExtensionLoader.getExtensionLoader(com.apache.dubbo.rpc.Protocol.class).getExtension(extName);
- 首先還是獲取 擴(kuò)展點(diǎn)
Protocol對(duì)應(yīng)得擴(kuò)展類加載器,接著調(diào)用getExtension方法獲取指定得擴(kuò)展實(shí)現(xiàn),先查詢是否已經(jīng)緩存了相應(yīng)得擴(kuò)展實(shí)例,若沒查到會(huì)調(diào)用createExtension方法去創(chuàng)建當(dāng)前指定得擴(kuò)展實(shí)現(xiàn)。 - 調(diào)用
createExtension方法時(shí),先從當(dāng)前擴(kuò)展點(diǎn)實(shí)現(xiàn)class實(shí)例緩存中查找,若是沒有這個(gè)擴(kuò)展名,說明傳入得參數(shù)有問題,會(huì)直接拋出異常。然后再從對(duì)象緩存實(shí)例cachedClasses中查找是否已經(jīng)實(shí)例化了,沒找到,實(shí)例化之后放入對(duì)象緩存EXTENSION_INSTANCES中。 - 調(diào)用
injectExtension方法注入其他得擴(kuò)展點(diǎn)實(shí)現(xiàn)(IOC),同上面Step13 - 獲取當(dāng)前擴(kuò)展點(diǎn)得所有包裝類
cachedWrapperClasses,循環(huán)調(diào)用包裝類得有參構(gòu)造器(參數(shù)就是擴(kuò)展點(diǎn)類型)實(shí)例化,之后也會(huì)調(diào)用injectExtension方法為包裝類也注入其他得擴(kuò)展點(diǎn)實(shí)現(xiàn),并返回最后一個(gè)包裝類得實(shí)例(每個(gè)Wrapper類都會(huì)包裝一個(gè)之前得擴(kuò)展實(shí)現(xiàn) 即: A -> B(A) -> C(B), 最后返回 C這個(gè)包裝類)(AOP)。而當(dāng)前擴(kuò)展點(diǎn)Protocol有兩個(gè)包裝類實(shí)現(xiàn):ProtocolFilterWrapper和ProtocolListenerWrapper。所以,當(dāng)獲取適配擴(kuò)展實(shí)現(xiàn)時(shí):
- 若是動(dòng)態(tài)生成得,則
getAdaptiveExtion() -> getExtension("dubbo或者是 url.getProtocol()的值")
-> injectExtension(T instance) (真正執(zhí)行調(diào)用的擴(kuò)展實(shí)現(xiàn))
-> injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance))
( A -> B(A) -> C(B) 最后返回 C這個(gè)包裝類) - 若是定義了適配類實(shí)現(xiàn),則直接走適配類的邏輯
- 若是動(dòng)態(tài)生成得,則

ExtensionLoader#createExtension
Ⅲ. ExtensionLoader#addExtension(String name, Class<?> clazz)
- 首先加載所有的擴(kuò)展類實(shí)現(xiàn),若
clazz沒有實(shí)現(xiàn)type擴(kuò)展點(diǎn),拋出異常。 (必須要實(shí)現(xiàn)擴(kuò)展點(diǎn),才可以添加) - 若
clazz是個(gè)接口,拋出異常。(擴(kuò)展實(shí)現(xiàn)不能是接口,否則不能實(shí)例化) - 若
clazz上沒有帶有@Adaptive注解- 若 擴(kuò)展名
name為空,拋出異常。 (擴(kuò)展名不能為空) - 若 緩存擴(kuò)展實(shí)現(xiàn)
cachedClasses中已經(jīng)存在當(dāng)前擴(kuò)展名,拋出異常。(擴(kuò)展名不能重復(fù)) - 若以上兩個(gè)條件都不滿足,則將擴(kuò)展名與擴(kuò)展點(diǎn)建立映射關(guān)系 , 緩存到
cachedNames,cachedClasses中。
- 若 擴(kuò)展名
- 若
clazz上帶有@Adaptive注解- 若適配類
cachedAdaptiveClass不為空,拋出異常。(擴(kuò)展點(diǎn)的適配類只能有一個(gè)) - 為空的話,將當(dāng)前
clazz作為擴(kuò)展點(diǎn)的適配類,賦值給cachedAdaptiveClass
添加擴(kuò)展實(shí)現(xiàn)的邏輯還是比較簡單的。通過API的方式動(dòng)態(tài)添加擴(kuò)展實(shí)現(xiàn),可以不通過配置文件的方式(挺實(shí)用的)
- 若適配類

ExtensionLoader#addExtension
Ⅳ ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(url, key, group) :
- 找到 ExtensionLoader的 getActivateExtension方法:
// ExtensionLoader
public List<T> getActivateExtension(URL url, String key, String group = consumer) {
// 獲取 URL 中指定 key對(duì)應(yīng)的值
// 以 ProtocolFilterWrappe r的 buildInvokerChain 方法為例(任何一個(gè)擴(kuò)展點(diǎn)都可)
// key = REFERENCE_FILTER_KEY,group = consumer
// 可以進(jìn)行如下改造 ( Filter自定義編排化):
// 1. URL url = invoker.getUrl().addParameter(REFERENCE_FILTER_KEY,"-default");
// 2. URL url = invoker.getUrl().addParameter(REFERENCE_FILTER_KEY,"-cache,actives");
// 3. URL url = invoker.getUrl().addParameter(REFERENCE_FILTER_KEY,"-default,actives");
// 你知道上面幾種擴(kuò)展方式,最后返回的 Filter擴(kuò)展實(shí)現(xiàn)有哪些么 ?
String value = url.getParameter(key);
// value值用于控制激活擴(kuò)展點(diǎn)是否加載,為空表示不指定激活擴(kuò)展點(diǎn)名稱,從所有的激活擴(kuò)展實(shí)現(xiàn)中根據(jù) @Activate 注解跟指定的組進(jìn)行篩選,若是需要指定,定義的擴(kuò)展點(diǎn)名稱需要以 "," 分隔開
return getActivateExtension(url, StringUtils.isEmpty(value) ? null : COMMA_SPLIT_PATTERN.split(value), group);
}
// ExtensionLoader
public List<T> getActivateExtension(URL url, String[] values, String group) {
List<T> exts = new ArrayList<>();
List<String> names = values == null ? new ArrayList<>(0) : Arrays.asList(values);
// 如果指定的擴(kuò)展的名稱不包括 -default(排除SPI中的實(shí)現(xiàn))標(biāo)識(shí)符,則從所有已經(jīng)加載的激活擴(kuò)展點(diǎn)中找到滿足條件的,否則只加載指定的激活擴(kuò)展點(diǎn)
if (!names.contains(REMOVE_VALUE_PREFIX + DEFAULT_KEY)) {
getExtensionClasses();
for (Map.Entry<String, Object> entry : cachedActivates.entrySet()) {// 遍歷激活擴(kuò)展點(diǎn)的所有實(shí)現(xiàn)
String name = entry.getKey();
Object activate = entry.getValue();
String[] activateGroup, activateValue;
if (activate instanceof Activate) {
activateGroup = ((Activate) activate).group();
activateValue = ((Activate) activate).value();
} else if (activate instanceof com.alibaba.dubbo.common.extension.Activate) {
activateGroup = ((com.alibaba.dubbo.common.extension.Activate) activate).group();
activateValue = ((com.alibaba.dubbo.common.extension.Activate) activate).value();
} else {
continue;
}
if (isMatchGroup(group, activateGroup) // 如果當(dāng)前需要加載的組在激活擴(kuò)展點(diǎn)實(shí)現(xiàn)指定的組中才算匹配
&& !names.contains(name)
&& !names.contains(REMOVE_VALUE_PREFIX + name)
&& isActive(activateValue, url)) { // URL中至少要有 activateValue[] 中的其中一個(gè)key,當(dāng)前激活擴(kuò)展點(diǎn)實(shí)現(xiàn)才會(huì)被使用
exts.add(getExtension(name));
}
}
exts.sort(ActivateComparator.COMPARATOR); // 按照自然序排序
}
List<T> usrs = new ArrayList<>();
// 若names不為空,表示指定了擴(kuò)展點(diǎn)名稱,找到不是以排除標(biāo)識(shí)符'-'開頭并且不包括要排除(-name)的擴(kuò)展點(diǎn)名稱,獲取其擴(kuò)展實(shí)現(xiàn)
for (int i = 0; i < names.size(); i++) {
String name = names.get(i);
if (!name.startsWith(REMOVE_VALUE_PREFIX)
&& !names.contains(REMOVE_VALUE_PREFIX + name)) {
if (DEFAULT_KEY.equals(name)) {
if (!usrs.isEmpty()) {
exts.addAll(0, usrs);
usrs.clear();
}
} else {
usrs.add(getExtension(name));
}
}
}
if (!usrs.isEmpty()) {
exts.addAll(usrs);
}
return exts;
}
- 上述就是Dubbo源碼中經(jīng)常出現(xiàn)的獲取擴(kuò)展實(shí)現(xiàn)的代碼,了解了其原理之后,后面我們解讀源碼時(shí),會(huì)更清晰自然了。
- ? 文章要是勘誤或者知識(shí)點(diǎn)說的不正確,歡迎評(píng)論,畢竟這也是作者通過閱讀源碼獲得的知識(shí),難免會(huì)有疏忽!
- ? 要是感覺文章對(duì)你有所幫助,不妨點(diǎn)個(gè)關(guān)注,或者移駕看一下作者的其他文集,也都是干活多多哦,文章也在全力更新中。
- ? 著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請注明出處!


