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)分明,清晰,可擴展性特別好。讀次源碼感嘆一下!

曾經(jīng)我再封裝一些框架的時候,總是加入一些不必要的依賴,比如封裝ribbon,支持多個配置中心的時候,就會發(fā)現(xià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)擴張,就是依靠動態(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);

}


接下來就是進入到?ExtensionLoader,SpiExtensionFactory?的?getExtension 服務(wù),會調(diào)用ExtensionLoader的?getExtensionLoader方法。然后通過調(diào)用?getAdaptiveExtension,加載自適應(yīng)點。好了,我們進入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)擴張類。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);

然后進入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)擴展點的? 你看,上面通過IOC 和 spiExtensionFactory 將所有的key = value 形式的類都加載好了,存儲的形式是map <key, class<?> class>. 那我們什么時候用哪個接口的實現(xiàn)類呢? 比如說:

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


那應(yīng)該使用哪個實現(xiàn)類呢? 這就是自適應(yīng)擴展點。首先會產(chǎn)生自適應(yīng)擴展點的代理,然后 在通過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)擴展點。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容