版本
2.5.7
ExtensionLoader機(jī)制
Dubbo 的擴(kuò)展點(diǎn)加載從 JDK 標(biāo)準(zhǔn)的 SPI (Service Provider Interface) 擴(kuò)展點(diǎn)發(fā)現(xiàn)機(jī)制加強(qiáng)而來(lái)铝穷。
Dubbo 改進(jìn)了 JDK 標(biāo)準(zhǔn)的 SPI 的以下問(wèn)題:
- JDK 標(biāo)準(zhǔn)的 SPI 會(huì)?次性實(shí)例化擴(kuò)展點(diǎn)所有實(shí)現(xiàn)钠怯,如果有擴(kuò)展實(shí)現(xiàn)初始化很耗時(shí),但如果沒(méi)用上也加載曙聂,會(huì)很浪費(fèi)資源晦炊;
- 如果擴(kuò)展點(diǎn)加載失敗,連擴(kuò)展點(diǎn)的名稱都拿不到了宁脊。?如:JDK 標(biāo)準(zhǔn)的 ScriptEngine刽锤,通過(guò)getName()獲取腳本類型的名稱,但如果 RubyScriptEngine 因?yàn)樗蕾嚨?jruby.jar 不存在朦佩,導(dǎo)致 RubyScriptEngine類加載失敗并思,這個(gè)失敗原因被吃掉了,和 ruby對(duì)應(yīng)不起來(lái)语稠,當(dāng)用戶執(zhí)行ruby 腳本時(shí)宋彼,會(huì)報(bào)不支持ruby,而不是真正失敗的原因仙畦;
- 增加了對(duì)擴(kuò)展點(diǎn) IoC 和 AOP 的支持输涕,?個(gè)擴(kuò)展點(diǎn)可以直接 setter 注?其它擴(kuò)展點(diǎn)。
以上內(nèi)容摘自官方文檔
ExtensionLoader的作用就是加載所有打上了@SPI
注解的接口慨畸,并根據(jù)配置進(jìn)行實(shí)例化莱坎、封裝,包括dubbo自己的服務(wù)調(diào)用寸士、暴露等功能,也是使用這種方式實(shí)現(xiàn)的檐什。
加載過(guò)程
1、實(shí)例化ExtensionLoader
構(gòu)造方法:
private ExtensionLoader(Class<?> type) {
this.type = type;
objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}
從代碼中可以看出弱卡,ExrtensionLoader
與SPI
是一一對(duì)應(yīng)的關(guān)系乃正,且每個(gè)封裝了SPI
的ExrtensionLoader
都保存了一個(gè)objectFactory
對(duì)象,而objectFactory
指向的都是ExrtensionLoader
的adaptive實(shí)現(xiàn)婶博,即AdaptiveExtensionFactory
瓮具。
1.1 AdaptiveExtension實(shí)例化過(guò)程
getAdaptiveExtension
的實(shí)例化使用了互斥鎖,檢查了是否有實(shí)例化異常 的緩存后,通過(guò)createAdaptiveExtension()
方法實(shí)例化對(duì)象名党。
在實(shí)例化的過(guò)程中叹阔,會(huì)再次執(zhí)行getExtensionClasses()
方法檢查是否已加載過(guò)配置文件。 以ExtensionFactory
舉例传睹,因?yàn)樵?code>dubbo-common的配置文件中耳幢,已經(jīng)默認(rèn)配置了ExtensionFactory
的兩個(gè)實(shí)現(xiàn),且其AdaptiveExtensionFactory
上打了@Adaptive
注解蒋歌,所以默認(rèn)情況下會(huì)返回緩存中,第一次加載配置文件時(shí)初始化的AdaptiveExtensionFactory
類委煤。
但如果緩存中沒(méi)有堂油,比如Protocol
就沒(méi)有在實(shí)現(xiàn)類類上注解@Adaptive
,也沒(méi)關(guān)系碧绞,ExrtensionLoader
會(huì)通過(guò)createAdaptiveExtensionClass()
方法生成代碼府框,再編譯成class字節(jié)碼。
Protocol接口源碼
package com.alibaba.dubbo.rpc;
import com.alibaba.dubbo.common.URL;
import com.alibaba.dubbo.common.extension.Adaptive;
import com.alibaba.dubbo.common.extension.SPI;
@SPI("dubbo")
public interface Protocol {
int getDefaultPort();
void destroy();
@Adaptive
<T> Exporter<T> export(Invoker<T> invoker) throws RpcException;
@Adaptive
<T> Invoker<T> refer(Class<T> type, URL url) throws RpcException;
}
Protocol動(dòng)態(tài)生成的實(shí)現(xiàn)類:
package com.alibaba.dubbo.rpc;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
public class Protocol$Adaptive implements com.alibaba.dubbo.rpc.Protocol {
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!");
}
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 com.alibaba.dubbo.rpc.Exporter export(com.alibaba.dubbo.rpc.Invoker arg0) throws com.alibaba.dubbo.rpc.RpcException {
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();
String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );
if(extName == null)
throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");
com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
return extension.export(arg0);
}
public com.alibaba.dubbo.rpc.Invoker refer(java.lang.Class arg0, com.alibaba.dubbo.common.URL arg1) throws com.alibaba.dubbo.rpc.RpcException {
if (arg1 == null)
throw new IllegalArgumentException("url == null");
com.alibaba.dubbo.common.URL url = arg1;
String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );
if(extName == null)
throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");
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);
}
}
可以看出讥邻,打了@Adaotive
注解的方法迫靖,都會(huì)動(dòng)態(tài)生成根據(jù)URL獲取信息的代碼,再組合成對(duì)應(yīng)的實(shí)現(xiàn)類兴使,并存儲(chǔ)到ExtensionLoader
的靜態(tài)變量中系宜,以后就可以通過(guò)ExtensionLoader
加載對(duì)應(yīng)的SPI
實(shí)現(xiàn),然后執(zhí)行業(yè)務(wù)邏輯发魄。
2盹牧、獲取ExtensionLoader
名稱 | 配置位置 | getExtension方法實(shí)現(xiàn) |
---|---|---|
SpiExtensionFactory | dubbo-common | 從ExtensionLoader 的靜態(tài)Map中讀取對(duì)象,若沒(méi)有励幼,則通過(guò)new 關(guān)鍵字創(chuàng)建ExtensionLoader
|
SpringExtensionFactory | dubbo-config/dubbo-config-spring | 從Spring上下文中讀取對(duì)象 |
ExtensionFactory
也是一種SPI
汰寓,所有的SPI
都是通過(guò)ExtensionLoader
來(lái)封裝、加載的苹粟,每個(gè)被封裝的對(duì)象中都由ExtensionFactory objectFactory
屬性持有一個(gè)ExtensionFactory
的Adaptive實(shí)現(xiàn)AdaptiveExtensionFactory
,而AdaptiveExtensionFactory
又由List<ExtensionFactory> factories
持有兩個(gè)對(duì)象SpiExtensionFactory
及SpringExtensionFactory
有滑。
他們的對(duì)比如下:
名稱 | 配置位置 | getExtension方法實(shí)現(xiàn) |
---|---|---|
SpiExtensionFactory | dubbo-common | 從ExtensionLoader 的靜態(tài)Map中讀取對(duì)象,若沒(méi)有嵌削,則通過(guò)new 關(guān)鍵字創(chuàng)建ExtensionLoader
|
SpringExtensionFactory | dubbo-config/dubbo-config-spring | 從Spring上下文中讀取對(duì)象 |
當(dāng)執(zhí)行AdaptiveExtensionFactory
的getExtension()
方法時(shí)毛好,它會(huì)輪詢二者之中有沒(méi)有要返回的對(duì)象,優(yōu)先SPI的方式苛秕。
總結(jié)
通過(guò)ExtensionLoader
機(jī)制睛榄,dubbo封裝了所有SPI實(shí)現(xiàn),并能夠使用@Adaptive
注解指定默認(rèn)的實(shí)現(xiàn)想帅,或者注解在方法上场靴,動(dòng)態(tài)將不同實(shí)現(xiàn)類的A\B\C方法組合成新的實(shí)現(xiàn)類,在靈活、可配旨剥、按需加載等方面做的非常出色咧欣。