Dubbo SPI (service provider interface)

最近拜讀dubbo 的源碼,其構(gòu)架設(shè)計是很精彩举瑰,基于插件式的構(gòu)架方式,靈活性可見一般此迅。此外dubbo 框架難得基 服務(wù)降級,服務(wù)路由耸序,服務(wù)發(fā)現(xiàn)與注冊,服務(wù)loadbalance 和 支持多種協(xié)議于一身的優(yōu)秀開源框架坎怪,很是值得學(xué)習(xí)和研究細(xì)細(xì)讀來的。此外整個框架的class 加載是根據(jù)統(tǒng)一的URL參數(shù)廓握。各個層次結(jié)構(gòu)分明搅窿,清晰嘁酿,可擴(kuò)展性特別好。讀次源碼感嘆一下男应!

曾經(jīng)我再封裝一些框架的時候闹司,總是加入一些不必要的依賴,比如封裝ribbon沐飘,支持多個配置中心的時候游桩,就會發(fā)現(xiàn),會把多個配置中心的依賴都加入進(jìn)去耐朴。開發(fā)支持多個校驗引擎的時候众弓,心理也是在琢磨著如何更好的切換到不同的校驗引擎。Dubbo SPI 提供一種很好的思路隔箍。根據(jù)統(tǒng)一的URL 配置信息谓娃,通過代理類按需加載!

前提:

(1). 理解 jdk SPI?

(2). 理解AVAssist 的產(chǎn)生class 字節(jié)碼蜒滩,Dubbo 中默認(rèn)使用AVAssist滨达,主要用來生成代理類

(3). 反射和動態(tài)代理(dubbo 的自適應(yīng)擴(kuò)張,就是依靠動態(tài)代理)

好了俯艰,我們來看看dubbo SPI具體的特性捡遍。

(1): dubbo SPI 根據(jù) protocol=com.XX.XXX.dubboProtocol, 的方式加載

(2): dubbo SPI 的IOC 特性竹握,dubboProtocol類的setXXX 方法画株,會通過反射的方式,將相應(yīng)的class instance啦辐, inject 到 dubboProtocol谓传, 通過setXXX的方法。 后面將會有源碼芹关。

(3):dubbo SPI 的AOP 特性,?ProtocolFilterWrapper 和?ProtocolListenerWrapper這兩個類含有protocol 單構(gòu)造器续挟,放置在loader的私有屬性cachedWrapperClasses。ProtocolFilterWrapper在服務(wù)的暴露與引用的過程中侥衬,根據(jù)key 是provider還是consumer來構(gòu)建服務(wù)提供者和消費者調(diào)用過濾鏈诗祸。因此具有AOP 的性質(zhì)。ProtocolListenerWrapper也是在服務(wù)暴露與引用的過程中調(diào)用listener鏈轴总。

首先我們需要理解ExtensionLoader, ExtensionFactory這個類功偿。ExtensionFactory 的實現(xiàn)類有?SpiExtensionFactory,?SpringExtensionFactory. 默認(rèn)會加載AdaptiveExtensionFactory (@Adaptive 的注解)實現(xiàn)類脖含。

(1): SpiExtensionFactory:加載有@SPI 的注解的接口實現(xiàn)類投蝉。

(2):SpringExtensionFactory: 加載spring context bean 的beans。

默認(rèn)的實現(xiàn)類AdaptiveExtensionFactory瘩缆,依賴 List<ExtensionFactory> factories. 當(dāng)調(diào)用?ExtensionFactory 時候,會循環(huán)SpiExtensionFactory和SpringExtensionFactory着绊,獲得?Class?type 的 extension熟尉。

@SPI

public interface ExtensionFactory {

? ? T getExtension(Class type, String name);

}


接下來就是進(jìn)入到?ExtensionLoader斤儿,SpiExtensionFactory?的?getExtension 服務(wù)往果,會調(diào)用ExtensionLoader的?getExtensionLoader方法。然后通過調(diào)用?getAdaptiveExtension堕油,加載自適應(yīng)點肮之。好了戈擒,我們進(jìn)入ExtensionLoader,?getExtensionLoader 去看看會發(fā)生什么赘来。

但是再這之前犬辰,我們看看加載類的配置冰单。配置文件的路徑如下:

private static final StringSERVICES_DIRECTORY ="META-INF/services/";

private static final StringDUBBO_DIRECTORY ="META-INF/dubbo/";

private static final StringDUBBO_INTERNAL_DIRECTORY =DUBBO_DIRECTORY +"internal/";

ExtensionLoader? ? 會加載所有classpath 下诫欠,該目錄的實現(xiàn)類浴栽。其implement 的接口都必須有@SPI的注解典鸡。


里面的每個類的結(jié)構(gòu)都是 “name” = “com.XXX.XXXX.registryProtocol”的方式來實現(xiàn)的萝玷。首先我們來看下ExtensionLoader主要邏輯:

@SuppressWarnings("unchecked")

public static ExtensionLoadergetExtensionLoader(Class type) {

if (type ==null)

throw new IllegalArgumentException("Extension type == null");

? ? if (!type.isInterface()) {

throw new IllegalArgumentException("Extension type(" + type +") is not interface!");

? ? }

if (!withExtensionAnnotation(type)) {

throw new IllegalArgumentException("Extension type(" + type +

") is not extension, because WITHOUT @" +SPI.class.getSimpleName() +" Annotation!");

? ? }

ExtensionLoader loader = (ExtensionLoader)EXTENSION_LOADERS.get(type);

? ? if (loader ==null) {

EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader(type));

? ? ? ? loader = (ExtensionLoader)EXTENSION_LOADERS.get(type);

? ? }

return loader;

}

先從EXTENSION_LOADERS cache 中取出 type 類型的?ExtensionLoader球碉, 比如protocol睁冬。 如果沒有就會new 一個ExtensionLoader. 我們來看一下new?ExtensionLoader的邏輯看疙。?

private ExtensionLoader(Class type) {

this.type = type;? ?

?objectFactory = (type == ExtensionFactory.class ?null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());

}

如果不是ExtensionFactory類型的狼荞,就獲取一個自適應(yīng)擴(kuò)張類。getAdaptiveExtension()拾积。

那么getAdaptiveExtension()是什么邏輯呢拓巧?

public T getAdaptiveExtension() {

Object instance =cachedAdaptiveInstance.get();

? ? if (instance ==null) {

if (createAdaptiveInstanceError ==null) {

synchronized (cachedAdaptiveInstance) {

instance =cachedAdaptiveInstance.get();

? ? ? ? ? ? ? ? if (instance ==null) {

try {

instance = createAdaptiveExtension();

? ? ? ? ? ? ? ? ? ? ? ? cachedAdaptiveInstance.set(instance);

? ? ? ? ? ? ? ? ? ? }catch (Throwable t) {

createAdaptiveInstanceError = t;

? ? ? ? ? ? ? ? ? ? ? ? throw new IllegalStateException("fail to create adaptive instance: " + t.toString(), t);

? ? ? ? ? ? ? ? ? ? }

}

}

}else {

throw new IllegalStateException("fail to create adaptive instance: " +createAdaptiveInstanceError.toString(), createAdaptiveInstanceError);

? ? ? ? }

}

return (T) instance;

}

首先也是從cachedAdaptiveInstance 獲取肛度, 如果獲取不到承耿,通過調(diào)用createAdaptiveExtension()伪煤。 我們看看createAdaptiveExtension的邏輯抱既。

private T createAdaptiveExtension() {

try {

return injectExtension((T) getAdaptiveExtensionClass().newInstance());

? ? }catch (Exception e) {

throw new IllegalStateException("Can not create adaptive extension " +type +", cause: " + e.getMessage(), e);

? ? }

}

首先是通過getAdaptiveExtensionClass,然后實列化蚀之,通過injectExtension 來注入,injectExtension就是上面提到的 IOC 特性寿谴,會通過setXXX() 方法拭卿,注入對象實列贱纠。我們先看

private Map>getExtensionClasses() {

Map> classes =cachedClasses.get();

? ? if (classes ==null) {

synchronized (cachedClasses) {

classes =cachedClasses.get();

? ? ? ? ? ? if (classes ==null) {

classes = loadExtensionClasses();

? ? ? ? ? ? ? ? cachedClasses.set(classes);

? ? ? ? ? ? }

}

}

return classes;

}

首先從cachedClasses 緩存中獲取class谆焊,如果獲取不到辖试,就調(diào)用loadExtensionClasses();劈狐,加載extension classes.?

private Map>loadExtensionClasses() {

final SPI defaultAnnotation =type.getAnnotation(SPI.class);

? ? if (defaultAnnotation !=null) {

String value = defaultAnnotation.value();

? ? ? ? if ((value = value.trim()).length() >0) {

String[] names =NAME_SEPARATOR.split(value);

? ? ? ? ? ? if (names.length >1) {

throw new IllegalStateException("more than 1 default extension name on extension " +type.getName()

+": " + Arrays.toString(names));

? ? ? ? ? ? }

if (names.length ==1)cachedDefaultName = names[0];

? ? ? ? }

}

Map> extensionClasses =new HashMap>();

? ? loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY);

? ? loadDirectory(extensionClasses, DUBBO_DIRECTORY);

? ? loadDirectory(extensionClasses, SERVICES_DIRECTORY);

? ? return extensionClasses;

}

首先是設(shè)置cachedDefaultName 的默認(rèn)名字莲兢, 然后去load 各個目錄下的class续膳, key = value 的形式。

Map> extensionClasses =new HashMap>();

loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY);

loadDirectory(extensionClasses, DUBBO_DIRECTORY);

loadDirectory(extensionClasses, SERVICES_DIRECTORY);

然后進(jìn)入loadResource 的方法谒兄,我們就明白key value 的形式是如何解析的承疲。最后我們調(diào)用loadClass 方法鸥咖,將加載好的class 放入到對應(yīng)的緩存中。帶有adaptive 注解的放入cachedAdaptiveClass绵咱, wrapper的class悲伶,放入到?cachedWrapperClasses中去。標(biāo)記有@Active 的類注入到cachedActivates 緩存中去钠绍。

將所有的key? = value 形式加載完畢之后柳爽,我們要看看IOC 是如何作用的磷脯。

privateTinjectExtension(T instance){

? ? try {

? ? ? ? if (objectFactory != null) {

? ? ? ? ? ? // 遍歷目標(biāo)類的所有方法? ? ? ? ? ? for (Method method : instance.getClass().getMethods()) {

? ? ? ? ? ? ? ? // 檢測方法是否以 set 開頭娩脾,且方法僅有一個參數(shù),且方法訪問級別為 public? ? ? ? ? ? ? ? if (method.getName().startsWith("set")

? ? ? ? ? ? ? ? ? ? && method.getParameterTypes().length == 1? ? ? ? ? ? ? ? ? ? && Modifier.isPublic(method.getModifiers())) {

? ? ? ? ? ? ? ? ? ? // 獲取 setter 方法參數(shù)類型? ? ? ? ? ? ? ? ? ? Class<?> pt = method.getParameterTypes()[0];

? ? ? ? ? ? ? ? ? ? try {

? ? ? ? ? ? ? ? ? ? ? ? // 獲取屬性名俩功,比如 setName 方法對應(yīng)屬性名 name? ? ? ? ? ? ? ? ? ? ? ? String property = method.getName().length() > 3 ?

? ? ? ? ? ? ? ? ? ? ? ? ? ? method.getName().substring(3, 4).toLowerCase() +

? ? ? ? ? ? ? ? ? ? ? ? ? ? method.getName().substring(4) : "";

? ? ? ? ? ? ? ? ? ? ? ? // 從 ObjectFactory 中獲取依賴對象? ? ? ? ? ? ? ? ? ? ? ? Object object = objectFactory.getExtension(pt, property);

? ? ? ? ? ? ? ? ? ? ? ? if (object != null) {

? ? ? ? ? ? ? ? ? ? ? ? ? ? // 通過反射調(diào)用 setter 方法設(shè)置依賴? ? ? ? ? ? ? ? ? ? ? ? ? ? method.invoke(instance, object);

? ? ? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? ? ? } catch (Exception e) {

? ? ? ? ? ? ? ? ? ? ? ? logger.error("fail to inject via method...");

? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? }

? ? ? ? ? ? }

? ? ? ? }

? ? } catch (Exception e) {

? ? ? ? logger.error(e.getMessage(), e);

? ? }

? ? return instance;

}

通過 Object object = objectFactory.getExtension(pt, property) 和?method.invoke(instance, object) 將adaptive 的類注入的 帶有setXXX方法類中诡蜓。其中objectFactory就是spiExtensionFactory蔓罚。?

那么有沒有想過到底是如何使用 自適應(yīng)擴(kuò)展點的脚粟? 你看蘸朋,上面通過IOC 和 spiExtensionFactory 將所有的key = value 形式的類都加載好了藕坯,存儲的形式是map <key, class<?> class>. 那我們什么時候用哪個接口的實現(xiàn)類呢? 比如說:

package com.alibaba.dubbo.rpc; 下的 protocol吐根。 實現(xiàn)類有


那應(yīng)該使用哪個實現(xiàn)類呢拷橘? 這就是自適應(yīng)擴(kuò)展點冗疮。首先會產(chǎn)生自適應(yīng)擴(kuò)展點的代理,然后 在通過URL 的配置信息另萤,傳入name四敞, 獲取相應(yīng)的加載類拔妥,這就是key value 的原因。?

代理類是什么樣呢癌蚁?代理類是通過javassit 產(chǎ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.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);

}

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);

}}

String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() ); 就是從 URL 獲取 name, 如果獲取不到肛鹏,就默認(rèn)使用dubbo恩沛,通過?getExtension(extName),就可以獲取到 加載類:?DubboProtocol

那URL 是啥樣子呢:dubbo://172.27.238.135:20880/com.mastercard.api.service.DemoService?anyhost=true&application=dubbo-provider&bind.ip=172.27.238.135&bind.port=20880&default.timeout=5000&dubbo=2.6.2&generic=false&interface=com.mastercard.api.service.DemoService&methods=sayHello&pid=71712&revision=1.0.0&side=provider&timestamp=1549356336726&version=1.0.0

其實里面的很多內(nèi)容很精彩芒珠,需要自己慢慢品味皱卓。后面具體分析下自適應(yīng)擴(kuò)展點部逮。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末兄朋,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子傅事,更是在濱河造成了極大的恐慌,老刑警劉巖灼芭,帶你破解...
    沈念sama閱讀 222,681評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件彼绷,死亡現(xiàn)場離奇詭異茴迁,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)猜旬,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,205評論 3 399
  • 文/潘曉璐 我一進(jìn)店門洒擦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來怕膛,“玉大人褐捻,你說我怎么就攤上這事∶潦ǎ” “怎么了板壮?”我有些...
    開封第一講書人閱讀 169,421評論 0 362
  • 文/不壞的土叔 我叫張陵个束,是天一觀的道長茬底。 經(jīng)常有香客問我,道長阱表,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,114評論 1 300
  • 正文 為了忘掉前任门岔,我火速辦了婚禮烤送,結(jié)果婚禮上帮坚,老公的妹妹穿的比我還像新娘。我一直安慰自己试和,他們只是感情好阅悍,可當(dāng)我...
    茶點故事閱讀 69,116評論 6 398
  • 文/花漫 我一把揭開白布节视。 她就那樣靜靜地躺著,像睡著了一般晌畅。 火紅的嫁衣襯著肌膚如雪寡痰。 梳的紋絲不亂的頭發(fā)上拦坠,一...
    開封第一講書人閱讀 52,713評論 1 312
  • 那天剩岳,我揣著相機(jī)與錄音,去河邊找鬼晓铆。 笑死绰播,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的链蕊。 我是一名探鬼主播,決...
    沈念sama閱讀 41,170評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼逻谦,長吁一口氣:“原來是場噩夢啊……” “哼邦马!你這毒婦竟也來了勇婴?” 一聲冷哼從身側(cè)響起嘱腥,我...
    開封第一講書人閱讀 40,116評論 0 277
  • 序言:老撾萬榮一對情侶失蹤齿兔,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后添诉,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體医寿,經(jīng)...
    沈念sama閱讀 46,651評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡靖秩,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,714評論 3 342
  • 正文 我和宋清朗相戀三年花颗,在試婚紗的時候發(fā)現(xiàn)自己被綠了扩劝。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,865評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡棒呛,死狀恐怖簇秒,靈堂內(nèi)的尸體忽然破棺而出涵亏,到底是詐尸還是另有隱情,我是刑警寧澤拆内,帶...
    沈念sama閱讀 36,527評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站灵巧,受9級特大地震影響刻肄,放射性物質(zhì)發(fā)生泄漏融欧。R本人自食惡果不足惜噪馏,卻給世界環(huán)境...
    茶點故事閱讀 42,211評論 3 336
  • 文/蒙蒙 一欠肾、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧粹淋,春花似錦桃移、人聲如沸封豪。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,699評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至廓推,卻和暖如春刷袍,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背樊展。 一陣腳步聲響...
    開封第一講書人閱讀 33,814評論 1 274
  • 我被黑心中介騙來泰國打工呻纹, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留堆生,地道東北人。 一個月前我還...
    沈念sama閱讀 49,299評論 3 379
  • 正文 我出身青樓雷酪,卻偏偏與公主長得像淑仆,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子哥力,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,870評論 2 361

推薦閱讀更多精彩內(nèi)容