上一章咱們介紹了JDK的SPI機制,它旨在創建一種服務發現的規範。而Dubbo基於此根據框架的總體設計作了一些改進:java
Dubbo做爲一個微內核+插件的框架設計,其內核就是基於SPI機制動態地組裝插件。插件都是以接口的方式定義在框架中,每一個接口提供的服務也稱爲擴展點,表示每一個服務接口均可以根據不一樣的條件進行動態擴展,Dubbo的擴展點接口以@SPI註解標識。Dubbo自身的功能也是經過擴展點實現的,也就是Dubbo的全部功能點均可以被用戶自定義擴展所代替。那麼Dubbo的SPI機制到底是怎麼工做的呢?app
在classpath下放置META-INF/dubbo/以接口全限定名定義的文件,內容爲配置名=擴展實現類全限定名,多個實現類用換行符分隔。框架
META-INF/dubbo目錄是針對二次開發者的,dubbo自身加載擴展點配置的目錄有三個,依次是:ide
以Dubbo的協議接口Protocol爲例,在dubbo-rpc-default模塊中定義了DubboProtocol實現,目錄爲url
META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Protocol
文件中內容爲spa
dubbo=com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol
對應於dubbo的xml配置,即.net
<dubbo:protocol name="dubbo" />
JDK自帶的SPI機制使用ServiceLoader加載和獲取,Dubbo對於擴展點的的加載和獲取則是使用ExtensionLoader。大部分的擴展點都是經過ExtensionLoader預加載一個適配類,以Protocol爲例,調用方式以下:插件
Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
getAdaptiveExtension方法建立擴展點的適配類,建立的方式分爲兩種設計
對於1的方式很好理解,好比Compiler接口,它的實現類AdaptiveCompiler類上就有@Adaptive註解netty
@Adaptive public class AdaptiveCompiler implements Compiler { // 實現代碼省略 }
2的方式就較爲複雜,仍是以Protocol舉例,先看下Protocol接口的源碼:
@SPI("dubbo") public interface Protocol { int getDefaultPort(); @Adaptive <T> Exporter<T> export(Invoker<T> invoker) throws RpcException; @Adaptive <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException; void destroy(); }
Protocol接口定義了四個方法,其中export和refer方法都有@Adaptive註解,不一樣於1中類上的註解,這裏的註解是在方法上,這個地方要着重強調一下@Adaptive註解做用的位置。對於實現類上沒有此註解,而方法上有此註解的,適用於2方式。Protocol基於2方式生成的代碼以下:
package com.alibaba.dubbo.rpc; import com.alibaba.dubbo.common.extension.ExtensionLoader; public class Protocol$Adaptive implements com.alibaba.dubbo.rpc.Protocol { // 沒有@Adaptive註解的直接拋出UnsupportedOperationException 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!"); } // 有@Adaptive的方法,從參數裏獲取URL對象, // 獲取擴展點配置名,ExtensionLoader.getExtension(extName)匹配建立擴展點實現類對象 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); } }
生成的適配類對象,以擴展點接口+$Adaptive命名(如Protocol$Adaptive),實現了擴展點接口。沒有@Adaptive的方法直接拋出UnsupportedOperationException,對於有@Adaptive的方法,從參數中獲取URL對象,而後根據URL獲取擴展點配置名,再使用ExtensionLoader.getExtension(extName)匹配建立擴展點實現類對象。
這裏的URL是Dubbo自定義的類,它是Dubbo的統一數據模型,穿插於系統的整個執行過程。URL的數據格式以下
dubbo://192.168.2.100:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-consumer&check=false&dubbo=2.0.0&generic=false&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&owner=william&pid=7356&side=consumer×tamp=1416971340626
a. URL獲取擴展點配置名,經過key從URL的parameter中獲取
String extName = url.getParameter(key, defalutValue);
defalutValue取值於擴展點接口的@SPI的值,好比Protocol接口的defaultValue=dubbo
@SPI("dubbo") public interface Protocol {}
key的值來自於@Adaptive的value值,若是@Adaptive沒有設置value,默認爲擴展點接口的類的simpleName。若是key的值爲protocol時特殊處理:
String extName = url.getProtocol()
好比Transporter擴展點的bind方法
@SPI("netty") public interface Transporter { @Adaptive({"server", "transporter"}) Server bind(URL url, ChannelHandler handler) throws RemotingException; }
defaultValue=netty,key=server,transporter,獲取配置名的方法:
String extName = url.getParameter("server",url.getParameter("transporter", "netty"));
b. 擴展點建立完$Adaptive對象,具體調用時,根據URL參數獲取的配置名extName,查詢對應的擴展點實現,仍是以Protocol擴展點舉例
ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
getExtension(extName)方法匹配擴展點的實現類Class的配置名,找到對應的Class對象,執行newInstance方法建立實際的操做對象。extName=dubbo的Protocol擴展點實現類爲DubboProtocol。建立完實現類對象,會對此對象執行injectExtension方法,即對對象內的以set開頭,而且只有一個參數的public方法,執行IOC注入。
IOC注入也是以ExtensionFactory擴展點的方式實現,默認的方式是先以SPI機制獲取方法參數類型的實現,若是此方法參數類型非接口或沒有@SPI註解,則從Spring上下文中獲取。
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) : ""; 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); } } } }
接下來判斷擴展點的實現類中是否存在包裝類(Wrapper),Wrapper類指存在參數爲擴展點接口的構造方法的擴展點實現類。Wrapper類不是擴展點的真正實現,主要用於對真正的擴展點實現進行包裝。好比Protocol的Filter包裝類實現ProtocolFilterWrapper。
若是擴展點存在包裝類的Class,反射調用其構造方法,將真正的擴展點實現傳入,並再此執行injectExtension方法進行IOC注入。
Set<Class<?>> wrapperClasses = cachedWrapperClasses; if (wrapperClasses != null && !wrapperClasses.isEmpty()) { for (Class<?> wrapperClass : wrapperClasses) { instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance)); } }
至此,擴展點的調用來到了真正對應的擴展點實現類對象中。
若是須要同時加載多個擴展點實現,可使用自動激活。當擴展點的實現類上有@Activate註解,標識它是一個激活類。@Activate能夠配置group和value值,ExtensionLoader的getActivateExtension方法匹配並獲取符合條件的全部激活類。Dubbo內部使用激活類的主要在Protocol的Filter和Listener,後面再詳細介紹。
以上討論的擴展點的適配類,包裝類,激活類的實現,都要基於對SPI機制的配置文件的加載。Dubbo在獲取擴展點的任何擴展時,都會先判斷是否加載了配置文件,若是沒有,即執行loadExtensionClasses方法加載,loadExtensionClasses方法中對Dubbo的三個配置目錄分別加載,調用loadFile方法。
loadFile方法查找目錄下擴展點接口的全限定名的文件,過濾掉"#"的註釋內容,而後每行以"="分隔,獲取配置名和擴展點實現類的全限定名。
依次判斷是否爲Adaptive適配類,是否爲Wrapper包裝類,是否爲Activate激活類,並存儲到對應的變量或集合中去。詳細操做可見ExtensionLoader.loadFile方法。
最後貼上getExtension(name)方法的總體活動圖(右擊圖片打開新標籤頁查看大圖),來自大神的博客https://blog.csdn.net/quhongwei_zhanqiu/article/details/41577235