最近拜讀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×tamp=1549356336726&version=1.0.0
其實里面的很多內(nèi)容很精彩芒珠,需要自己慢慢品味皱卓。后面具體分析下自適應(yīng)擴(kuò)展點部逮。