看懂dubbo原始碼的第一大攔路虎就是ExtensionLoader類,否則dubbo原始碼根本閱讀不下去。
ExtensionLoader作為SPI介面的物件建立工廠實現了以下功能:
- 從類路徑classpath下META-INF/dubbo/SPI介面全限定名稱檔案載入SPI實現類(dubbo定義的可擴充套件介面),方便使用者擴充套件dubbo的功能
- 實現類似spring ioc的功能,實現依賴物件的自動注入。
- 通過Wrapper包裝器模式擴充套件SPI實現類功能,實現類似aop的功能,參考ProtocolFilterWrapper類
- 建立SPI介面的自適應實現類,自適應實現類能夠根據介面方法的入參URL屬性呼叫具體實現類。
獲取指定型別的擴充套件點載入器例項方法如下
private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<Class<?>, ExtensionLoader<?>>();
//返回SPI介面對應的ExtensionLoader<T>單例
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> 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<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
if (loader == null) {
EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
}
return loader;
}
//只能載入宣告瞭SPI的擴充套件介面
private static <T> boolean withExtensionAnnotation(Class<T> type) {
return type.isAnnotationPresent(SPI.class);
}
private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<String, Holder<Object>>();
//擴充套件載入器例項方法獲取指定名稱例項,使用cachedInstances
做快取,使用Holder包裝例項物件避免重複建立例項。
public T getExtension(String name) {
if (name == null || name.length() == 0)
throw new IllegalArgumentException("Extension name == null");
if ("true".equals(name)) {
return getDefaultExtension();
}
//通過獲取名稱對應的單例實現類
Holder<Object> holder = cachedInstances.get(name);
if (holder == null) {
cachedInstances.putIfAbsent(name, new Holder<Object>());
holder = cachedInstances.get(name);
}
Object instance = holder.get();
if (instance == null) {
//雙重檢查模式避免重複建立物件
synchronized (holder) {
instance = holder.get();
if (instance == null) {
instance = createExtension(name);
holder.set(instance);
}
}
}
return (T) instance;
}
public class Holder<T> {
private volatile T value;
public void set(T value) {
this.value = value;
}
public T get() {
return value;
}
}
private T createExtension(String name) {
//getExtensionClasses從classpath的讀取META-INF/services/,
//META-INF/dubbo/,META-INF/dubbo/internal/目錄 +
//T.class.getName()檔案獲取讀取配置資訊,getExtensionClasses程式碼省略,感興趣自行檢視對應程式碼
Class<?> clazz = getExtensionClasses().get(name);
if (clazz == null) {
throw findException(name);
}
try {
//對應指定spi介面實現類的快取,使用ConcurrentMap實現
T instance = (T) EXTENSION_INSTANCES.get(clazz);
if (instance == null) {
EXTENSION_INSTANCES.putIfAbsent(clazz, (T) clazz.newInstance());
instance = (T) EXTENSION_INSTANCES.get(clazz);
}
//實現spi例項的屬性依賴自動注入自適應實現類
injectExtension(instance);
//判斷該SPI介面有沒有包裝器實現類,如果存在建立包裝器鏈實現類似AOP方法攔截的功能,具體可以參考ProtocolFilterWrapper類
Set<Class<?>> wrapperClasses = cachedWrapperClasses;
if (wrapperClasses != null && wrapperClasses.size() > 0) {
for (Class<?> wrapperClass : wrapperClasses) {
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
}
}
return instance;
} catch (Throwable t) {
throw new IllegalStateException("Extension instance(name: " + name + ", class: " +
type + ") could not be instantiated: " + t.getMessage(), t);
}
}
//迴圈呼叫物件的setter方法注入依賴物件
private T injectExtension(T instance) {
try {
if (objectFactory != null) {
for (Method method : instance.getClass().getMethods()) {
if (method.getName().startsWith("set")
&& method.getParameterTypes().length == 1
&& Modifier.isPublic(method.getModifiers())) {
Class<?> pt = method.getParameterTypes()[0];
try {
String property = method.getName().length() > 3 ?
method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";
//這裡是重點,預設會使用SpiExtensionFactory獲取對應屬性的實現類,實際上返回的是自適應實現類
Object object = objectFactory.getExtension(pt, property);
if (object != null) {
method.invoke(instance, object);
}
} catch (Exception e) {
logger.error("fail to inject via method " + method.getName()
+ " of interface " + type.getName() + ": " + e.getMessage(), e);
}
}
}
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
return instance;
}
public class SpiExtensionFactory implements ExtensionFactory {
public <T> T getExtension(Class<T> type, String name) {
if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {
ExtensionLoader<T> loader = ExtensionLoader.getExtensionLoader(type);
//如果指定spi有多個擴充套件實現,會返回一個自適應的spi實現類
if (loader.getSupportedExtensions().size() > 0) {
return loader.getAdaptiveExtension();
}
}
return null;
}
}
//返回自適應的介面T的實現類
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;
}
private T createAdaptiveExtension() {
try {
return injectExtension((T) getAdaptiveExtensionClass().newInstance());
} catch (Exception e) {
throw new IllegalStateException("Can not create adaptive extenstion " + type + ", cause: " + e.getMessage(), e);
}
}
複製程式碼
getAdaptiveExtensionClass()方法運用了程式碼生成技術,非常晦澀,來看下com.alibaba.dubbo.rpc.Protocol SPI介面生成的自適應實現類程式碼如下 Protocol的介面定義如下,SPI介面都要使用@SPI註解宣告,值為預設實現類對應的名稱
@SPI("dubbo")
public interface Protocol {
/**
* 獲取預設埠,當使用者沒有配置埠時使用。
*
* @return 預設埠
*/
int getDefaultPort();
/**
* 暴露遠端服務:<br>
* 1. 協議在接收請求時,應記錄請求來源方地址資訊:RpcContext.getContext().setRemoteAddress();<br>
* 2. export()必須是冪等的,也就是暴露同一個URL的Invoker兩次,和暴露一次沒有區別。<br>
* 3. export()傳入的Invoker由框架實現並傳入,協議不需要關心。<br>
*
* @param <T> 服務的型別
* @param invoker 服務的執行體
* @return exporter 暴露服務的引用,用於取消暴露
* @throws RpcException 當暴露服務出錯時丟擲,比如埠已佔用
*/
@Adaptive
<T> Exporter<T> export(Invoker<T> invoker) throws RpcException;
/**
* 引用遠端服務:<br>
* 1. 當使用者呼叫refer()所返回的Invoker物件的invoke()方法時,協議需相應執行同URL遠端export()傳入的Invoker物件的invoke()方法。<br>
* 2. refer()返回的Invoker由協議實現,協議通常需要在此Invoker中傳送遠端請求。<br>
* 3. 當url中有設定check=false時,連線失敗不能丟擲異常,並內部自動恢復。<br>
*
* @param <T> 服務的型別
* @param type 服務的型別
* @param url 遠端服務的URL地址
* @return invoker 服務的本地代理
* @throws RpcException 當連線服務提供方失敗時丟擲
*/
@Adaptive
<T> Invoker<T> refer(Class<T> type, URL url) throws RpcException;
/**
* 釋放協議:<br>
* 1. 取消該協議所有已經暴露和引用的服務。<br>
* 2. 釋放協議所佔用的所有資源,比如連線和埠。<br>
* 3. 協議在釋放後,依然能暴露和引用新的服務。<br>
*/
void destroy();
}
複製程式碼
使用程式碼生成技術生成的自適應動態代理類
package com.alibaba.dubbo.rpc;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
public class Protocol$Adpative 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.Invoker {
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 java.lang.Class {
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);
}
}
複製程式碼
這個代理的作用是從方法入參獲取url物件(第一篇文章介紹該物件用於呼叫承載配置資訊)獲取Protocol請求協議資訊,然後代理到對應協議的實現類去處理請求。
綜上所述,ExtensionLoader實現了從類載入路徑發現指定SPI介面實現類列表, 運用反射建立介面實現類以及依賴注入實現類對應屬性。對外暴露獲取指定名稱例項方法和基於代理建立的自適應例項,其中自適應例項實現非常巧妙,能夠根據請求配置資訊URL動態載入實現類,實現按需載入。 dubbo原始碼比較晦澀難懂的一大原因就是使用了比較多的程式碼生成,然後自動編譯技術,程式碼可讀性不高,完全可以自己實現一個預設的代理類,缺點是每個SPI介面可能都需要實現這樣一個沒有任何邏輯的代理類,程式碼重複性較高。