dubbo SPI擴展框架
- dubbo採用微內核+插件式擴展體系,獲得極佳的擴展性。
- dubbo沒有藉助spring,guice等IOC框架來管理,而是運用JDK SPI思路自己實現了一個IOC框架,減少了對外部框架的依賴
- 在Dubbo中,SPI是一個非常核心的機制,貫穿在幾乎所有的流程中。搞懂這塊內容,是接下來了解Dubbo更多源碼的關鍵因素。
- Dubbo是基於Java原生SPI機制思想的一個改進,所以,先從JAVA SPI機制開始瞭解什麼是SPI以後再去學習Dubbo的SPI,就比較容易了
關於JAVA 的SPI機制
- SPI全稱(service provider interface),是JDK內置的一種服務提供發現機制,
- 目前市面上有很多框架都是用它來做服務的擴展發現,大家耳熟能詳的如JDBC、日誌框架都有用到;
- 簡單來說,它是一種動態替換髮現的機制。
- 舉個簡單的例子,
- 如果我們定義了一個規範,需要第三方廠商去實現,
- 那麼對於我們應用方來說,只需要集成對應廠商的插件,既可以完成對應規範的實現機制。
- 形成一種插拔式的擴展手段。
- SPI規範總結
- 實現SPI,就需要按照SPI本身定義的規範來進行配置,SPI規範如下:
- 需要在classpath下創建一個目錄,該目錄命名必須是:META-INF/services
- 在該目錄下創建一個properties文件,該文件需要滿足以下幾個條件
- 文件名必須是擴展的接口的全路徑名稱
- 文件內部描述的是該擴展接口的所有實現類
- 文件的編碼格式是UTF-8
- 通過java.util.ServiceLoader的加載機制來發現
- SPI的實際應用
- SPI在很多地方有應用,大家可以看看最常用的java.sql.Driver驅動。
- JDK官方提供了java.sql.Driver這個驅動擴展點,但是你們並沒有看到JDK中有對應的Driver實現。
- 那在哪裏實現呢?
- 以連接Mysql爲例,我們需要添加mysql-connector-java依賴。
- 你們可以在這個jar包中找到SPI的配置信息。
- 所以java.sql.Driver由各個數據庫廠商自行實現。
- SPI的缺點
- JDK標準的SPI會一次性加載實例化擴展點的所有實現
- 就是如果你在META-INF/service下的文件裏面加了N個實現類,那麼JDK啓動的時候都會一次性全部加載。
- 那麼如果有的擴展點實現初始化很耗時或者如果有些實現類並沒有用到,那麼會很浪費資源
- 如果擴展點加載失敗,會導致調用方報錯,而且這個錯誤很難定位到是這個原因
- JDK標準的SPI會一次性加載實例化擴展點的所有實現
- 實現SPI,就需要按照SPI本身定義的規範來進行配置,SPI規範如下:
dubbo擴展框架特性
- 1). 內嵌在dubbo中
- 2). 支持通過SPI文件聲明擴展實現(interfce必須有@SPI註解),
- 格式爲extensionName=extensionClassName,extensionName類似於spring的beanName
- 3). 支持通過配置指定extensionName來從SPI文件中選出對應實現
Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getExtension("defineProtocol");
Dubbo SPI機制源碼閱讀
Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class). getAdaptiveExtension();
- 把上面這段代碼分成兩段,一段是getExtensionLoader、 另一段是getAdaptiveExtension。
-
- 第一段是通過一個Class參數去獲得一個ExtensionLoader對象,有點類似一個工廠模式。
- 第二段getAdaptiveExtension,去獲得一個自適應的擴展點
Extension源碼的結構
Protocol 源碼
- 一個是在類級別上的@SPI(“dubbo”)
- @SPI 表示當前這個接口是一個擴展點,可以實現自己的擴展實現,默認的擴展點是DubboProtocol。
- 另一個是@Adaptive
- @Adaptive 表示一個自適應擴展點,在方法級別上,會動態生成一個適配器類
/**
* Protocol. (API/SPI, Singleton, ThreadSafe)
*
* @author william.liangf
*/
@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();
}
getExtensionLoader
- 該方法需要一個Class類型的參數,該參數表示希望加載的擴展點類型,該參數必須是接口,且該接口必須被@SPI註解註釋,否則拒絕處理。
- 檢查通過之後首先會檢查ExtensionLoader緩存中是否已經存在該擴展對應的ExtensionLoader,
- 如果有則直接返回,否則創建一個新的ExtensionLoader負責加載該擴展實現,同時將其緩存起來。
@SuppressWarnings("unchecked")
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;
}
- ExtensionLoader提供了一個私有的構造函數,並且在這裏面對兩個成員變量type/objectFactory進行賦值。而objectFactory賦值的意義是什麼呢?先留個懸念
private ExtensionLoader(Class < ? > type){
this.type = type;
objectFactory = (type == ExtensionFactory.class ? null :
ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}
getAdaptiveExtension
- 通過getExtensionLoader獲得了對應的ExtensionLoader實例以後,
- 再調用getAdaptiveExtension()方法來獲得一個自適應擴展點。
-
ps:簡單對自適應擴展點做一個解釋,大家一定了解過適配器設計模式,而這個自適應擴展點實際上就是一個適配器。
-
主要做幾個事情:
- 從cacheAdaptiveInstance 這個內存緩存中獲得一個對象實例
- 如果實例爲空,說明是第一次加載,則通過雙重檢查鎖的方式去創建一個適配器擴展點
- createAdaptiveExtension
-
這段代碼裏面有兩個結構,一個是injectExtension. 另一個是getAdaptiveExtensionClass()
-
-
-
getAdaptiveExtensionClass()
-
從類名來看,是獲得一個適配器擴展點的類。
-
在這段代碼中,做了兩個事情:
- getExtensionClasses() 加載所有路徑下的擴展點
- createAdaptiveExtensionClass() 動態創建一個擴展點
- cachedAdaptiveClass這裏有個判斷,用來判斷當前Protocol這個擴展點是否存在一個自定義的適配器,
- 如果有,則直接返回自定義適配器,否則,就動態創建,這個值是在getExtensionClasses中賦值的,這塊代碼我們稍後再看
-
createAdaptiveExtensionClass
-
動態生成適配器代碼,以及動態編譯:
- createAdaptiveExtensionClassCode, 動態創建一個字節碼文件。返回code這個字符串
- 通過compiler.compile進行編譯(默認情況下使用的是javassist)
- 通過ClassLoader加載到jvm中
-
-
code的字節碼內容://創建一個適配器擴展點。(創建一個動態的字節碼文件) private Class<?> createAdaptiveExtensionClass() { //生成字節碼代碼 String code = createAdaptiveExtensionClassCode(); //獲得類加載器 ClassLoader classLoader = findClassLoader(); com.alibaba.dubbo.common.compiler.Compiler compiler = ExtensionLoader .getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class) .getAdaptiveExtension(); //動態編譯字節碼 return compiler.compile(code, classLoader); }
-
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); } }
Protocol$Adaptive的主要功能:
-
1. 從url或擴展接口獲取擴展接口實現類的名稱;
-
2.根據名稱,獲取實現類ExtensionLoader.getExtensionLoader(擴展接口類).getExtension(擴展接口實現類名稱),
-
然後調用實現類的方法。
-
-
-
- getExtensionClasses() 加載所有路徑下的擴展點
-
需要明白一點dubbo的內部傳參基本上都是基於Url來實現的,也就是說Dubbo是基於URL驅動的技術
-
所以,適配器類的目的是在運行期獲取擴展的真正實現來調用,解耦接口和實現,
-
這樣的話要不我們自己實現適配器類,要不dubbo幫我們生成,而這些都是通過Adpative來實現。
-
-
-
getExtensionClasses
- 就是加載擴展點實現類了。
- 這段代碼主要做如下幾個事情:
- 從cachedClasses中獲得一個結果,
- 這個結果實際上就是所有的擴展點類,key對應name,value對應class
- 通過雙重檢查鎖進行判斷
- 調用loadExtensionClasses,去加載左右擴展點的實現
- 從cachedClasses中獲得一個結果,
- loadExtensionClasses
-
從不同目錄去加載擴展點的實現,在最開始的時候講到過的。
-
META-INF/dubbo ;META-INF/internal ; META-INF/services
-
主要邏輯:
-
獲得當前擴展點的註解,也就是Protocol.class這個類的註解,@SPI
- 判斷這個註解不爲空,則再次獲得@SPI中的value值
- 如果value有值,也就是@SPI(“dubbo”),則講這個dubbo的值賦給cachedDefaultName。
- 這就是爲什麼我們能夠通過ExtensionLoader.getExtensionLoader(Protocol.class).getDefaultExtension() ,
- 能夠獲得DubboProtocol這個擴展點的原因
- 最後,通過loadFile去加載指定路徑下的所有擴展點。
- 也就是META-INF/dubbo;META-INF/internal;META-INF/services
-
-
-
// 此方法已經getExtensionClasses方法同步過。 private Map<String, Class<?>> loadExtensionClasses() { //type->Protocol.class //得到SPI的註解 final SPI defaultAnnotation = type.getAnnotation(SPI.class); if(defaultAnnotation != null) { //如果不等於空. String value = defaultAnnotation.value(); if(value != null && (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<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>(); loadFile(extensionClasses, DUBBO_INTERNAL_DIRECTORY); loadFile(extensionClasses, DUBBO_DIRECTORY); loadFile(extensionClasses, SERVICES_DIRECTORY); return extensionClasses; }
loadFile
- 解析指定路徑下的文件,獲取對應的擴展點,通過反射的方式進行實例化以後,put到extensionClasses這個Map集合中
-
private void loadFile(Map<String, Class<?>> extensionClasses, String dir) { String fileName = dir + type.getName(); try { Enumeration<java.net.URL> urls; ClassLoader classLoader = findClassLoader(); if (classLoader != null) { urls = classLoader.getResources(fileName); } else { urls = ClassLoader.getSystemResources(fileName); } if (urls != null) { while (urls.hasMoreElements()) { java.net.URL url = urls.nextElement(); try { BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream(), "utf-8")); try { String line = null; while ((line = reader.readLine()) != null) { final int ci = line.indexOf('#'); if (ci >= 0) line = line.substring(0, ci); line = line.trim(); if (line.length() > 0) { try { String name = null; int i = line.indexOf('='); if (i > 0) {//文件採用name=value方式,通過i進行分割 name = line.substring(0, i).trim(); line = line.substring(i + 1).trim(); } if (line.length() > 0) { Class<?> clazz = Class.forName(line, true, classLoader); //加載對應的實現類,並且判斷實現類必須是當前的加載的擴展點的實現 if (! type.isAssignableFrom(clazz)) { throw new IllegalStateException("Error when load extension class(interface: " + type + ", class line: " + clazz.getName() + "), class " + clazz.getName() + "is not subtype of interface."); } //判斷是否有自定義適配類,如果有,則在前面講過的獲取適配類的時候,直接返回當前的自定義適配類,不需要再動態創建 // 還記得在前面講過的getAdaptiveExtensionClass中有一個判斷嗎?是用來判斷cachedAdaptiveClass是不是爲空的。如果不爲空,表示存在自定義擴展點。也就不會去動態生成字節碼了。這個地方可以得到一個簡單的結論; // @Adaptive如果是加在類上, 表示當前類是一個自定義的自適應擴展點 //如果是加在方法級別上,表示需要動態創建一個自適應擴展點,也就是Protocol$Adaptive if (clazz.isAnnotationPresent(Adaptive.class)) { if(cachedAdaptiveClass == null) { cachedAdaptiveClass = clazz; } else if (! cachedAdaptiveClass.equals(clazz)) { throw new IllegalStateException("More than 1 adaptive class found: " + cachedAdaptiveClass.getClass().getName() + ", " + clazz.getClass().getName()); } } else { try { //如果沒有Adaptive註解,則判斷當前類是否帶有參數是type類型的構造函數,如果有,則認爲是 //wrapper類。這個wrapper實際上就是對擴展類進行裝飾. //可以在dubbo-rpc-api/internal下找到Protocol文件,發現Protocol配置了3個裝飾 //分別是,filter/listener/mock. 所以Protocol這個實例來說,會增加對應的裝飾器 clazz.getConstructor(type);// //得到帶有public DubboProtocol(Protocol protocol)的擴展點。進行包裝 Set<Class<?>> wrappers = cachedWrapperClasses; if (wrappers == null) { cachedWrapperClasses = new ConcurrentHashSet<Class<?>>(); wrappers = cachedWrapperClasses; } wrappers.add(clazz);//包裝類 ProtocolFilterWrapper(ProtocolListenerWrapper(Protocol)) } catch (NoSuchMethodException e) { clazz.getConstructor(); if (name == null || name.length() == 0) { name = findAnnotationName(clazz); if (name == null || name.length() == 0) { if (clazz.getSimpleName().length() > type.getSimpleName().length() && clazz.getSimpleName().endsWith(type.getSimpleName())) { name = clazz.getSimpleName().substring(0, clazz.getSimpleName().length() - type.getSimpleName().length()).toLowerCase(); } else { throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + url); } } } String[] names = NAME_SEPARATOR.split(name); if (names != null && names.length > 0) { Activate activate = clazz.getAnnotation(Activate.class); if (activate != null) { cachedActivates.put(names[0], activate); } for (String n : names) { if (! cachedNames.containsKey(clazz)) { cachedNames.put(clazz, n); } Class<?> c = extensionClasses.get(n); if (c == null) { extensionClasses.put(n, clazz); } else if (c != clazz) { throw new IllegalStateException("Duplicate extension " + type.getName() + " name " + n + " on " + c.getName() + " and " + clazz.getName()); } } } } } } } catch (Throwable t) { IllegalStateException e = new IllegalStateException("Failed to load extension class(interface: " + type + ", class line: " + line + ") in " + url + ", cause: " + t.getMessage(), t); exceptions.put(line, e); } } } // end of while read lines } finally { reader.close(); } } catch (Throwable t) { logger.error("Exception when load extension class(interface: " + type + ", class file: " + url + ") in " + url, t); } } // end of while urls } } catch (Throwable t) { logger.error("Exception when load extension class(interface: " + type + ", description file: " + fileName + ").", t); } }
階段性小結
-
截止到目前,我們已經把基於Protocol的自適應擴展點看完了。
-
也明白最終這句話應該返回的對象是什麼了.
Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class). getAdaptiveExtension();
也就是,這段代碼中,最終的protocol應該等於= Protocol$Adaptive
injectExtension
- 簡單來說,這個方法的作用,是爲這個自適應擴展點進行依賴注入。類似於spring裏面的依賴注入功能。
- 爲適配器類的setter方法插入其他擴展點或實現。
- 前面所有的相關類都加載完了,這裏會把成員變量,進行賦值(所謂依賴注入)
-
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); } }
getExtensionLoader這個方法中,會調用ExtensionLoader的私有構造方法進行初始化,其中有一個objectFactory.
- 這個是幹嘛的呢?
- 構建bean的工廠,方便Extension 託管給 Spring等容器
- objectFactory是一個 ExtensionFactory,用來獲取所有的託管bean