Dubbo強大的擴展能力,主要依賴於它本身實現的一套SPI機制,開發人員能夠根據dubbo的規範進行擴展示有的功能或者替換現有的實現,dubbo內部的功能的實現也都是經過的SPI來實現的,這是dubbo的功能高度可插拔的緣由。可是dubbo並無使用Java的SPI,之因此沒有使用Java自帶的SPI在官方文檔上有以下闡述。java
若是想要使用Java自帶的SPI,能夠參考java SPI (Service Provider Interface)緩存
在擴展類的jar包內,放置擴展點配置文件:META-INF/dubbo/接口全限定名,內容爲:配置名=擴展實現類全限定名,多個實現類用換行符分隔。
注意:這裏的配置文件是放在你本身的jar包內,不是dubbo自己的jar包內,Dubbo會全ClassPath掃描全部jar包內同名的這個文件,而後進行合併
dubbo SPI的主要是經過ExtensionLoader這個類來實現的,全部關鍵代碼也都在這個類中,使用方式有兩種,一種能夠手工指定,另一種是自動生成。ruby
有一個DuSPI的接口,接口中有一個sayHi的方法,而後經過dubbo的SPI機制進行調用其相關實現。ide
接口類url
必需要有@SPI註解 ,SPI註解能夠有一個value參數,表明默認的實現。spa
//接口類,定義了一個SayHello的方法,須要被實現 @SPI public interface DuSPI { public String SayHello(String hi); } //實現類,沒什麼好說的。 public class LocalDuSPI implements DuSPI { @Override public String SayHello(String hi) { return "FROM LocalDuSPI : "+hi; } }
擴展點配置.net
調用方式設計
public class DuSPIMain { private static final DuSPI duSPI = ExtensionLoader.getExtensionLoader(DuSPI.class).getExtension("local"); public static void main(String[] args) { String hi = duSPI.SayHello("擴展SPI"); System.out.println(hi); } }
輸出結果爲代理
上述就是很是簡單的手工指定實現類用例。getExtension("local");接收一個key值,這個key就是spi文件中的key,獲得的實現就是key對應的實現類。code
ExtensionLoader還有一種自動尋找擴展的方法,getAdaptiveExtension,不須要手工指定實現,而是在方法具體調用的時候自動發現其具體的實現類。例如Protocol擴展,它的export方法就是經過invoke的URL中的Protocol,自動的發現實現
private static final ProxyFactory proxyFactory = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension(); Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString())); Exporter<?> exporter = protocol.export(invoker);
經過對類註釋,能夠看到這個類主要實現如下幾個功能
自動注入關聯擴展點
自動Wrap上擴展點的Wrap類
缺省得到的擴展點的一個內部類Adaptive
這個類的實例需簡要有一個getExtensionLoader的靜態工廠方法,接收一個Class類型的對象,來獲取其實例。
@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; }
經過以上代碼能夠看出,傳入的對象必須是一個接口,而且必須是帶有@SPI註解,獲取完了之後,針對相應的接口類型會緩存到map中去。
根據key返回相應的實現類方法
getExtensionClass(String name)
首先會去在緩存裏面查找,看是否已經加載過這個類,若是加載過的話就直接返回,若是沒有,則配置文件中查找,而後進行加載。這樣就達到了它說的jdk的spi要所有加載全部擴展,而不是按需加載,若是有相應的擴展找不到就會報錯的或者一個類的加載很耗的問題。
建立擴展是使用的createExtension方法,類實例化之後,會查看有沒有對其餘擴展點的應用,若是有的話就調用injectExtension方法,去注入其餘的擴展類,這樣就達到了它所說的AOP的功能。
加載擴展類的方法是loadExtensionClasses
private Map<String, Class<?>> loadExtensionClasses() { 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; }
經過以上能夠得出如下結論
private void loadFile(Map<String, Class<?>> extensionClasses, String dir) { String fileName = dir + type.getName(); try { ... 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(); ... int i = line.indexOf('='); if (i > 0) { name = line.substring(0, i).trim(); line = line.substring(i + 1).trim(); } ... 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."); } 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 { clazz.getConstructor(type); ... 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); } ... } } } } } }
在生成對象引用的時候,並不直接生成實現對象,而是先生成一個代理對象,直到調用的時候,在根據傳遞的參數決定調用的是哪一個具體的實現類。Dubbo的SPI實現了自動發現機制。經過調用getAdaptiveExtension方法生成代理對象。在Protocol接口的使用中就用到了這個方法。
ServiceConfig類用須要有引用對象Protocol,這是標明dubbo的服務註冊所使用的協議。Protocol的實現類不少
可是在聲明的時候並不指名要調用哪一種協議的實現。
private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
代碼上調用了getAdaptiveExtension()方法,生成代理對象,因爲是代碼生成的代理對象,在dubbo中並無定義,因此只能把類文件拼裝的代碼的代碼打印出來,生成類文件的代碼是ExtensionLoader的createAdaptiveExtensionClassCode方法。生成的代理對象的類的定義以下:
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.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); } 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); } }
這是一個經過javassit自動生成的一個類,實現了Protocol接口,對於服務端內容導出發佈,實現了export方法,export 方法中,首先獲取Invoker的url,URL中獲取Protocol的protocol屬性的值,看是否進行了設置,默認爲dubbo,而後再調用dubboSPI(ExtensionLoader)機制中的getExtension方法,把Key傳入進去,以達到自動獲取Protocol的目的,而後找到真正的實現類,再調用其export方法。若是採用zk做爲註冊中心,經過配置<dubbo:registry address="zookeeper://localhost:2181" />
這樣取得的key是就是registry,真正的實現類則是
com.alibaba.dubbo.registry.integration.RegistryProtocol
固然,上述只是講述了dubbo spi設計的一個大概的思路,還有不少的其餘的細節問題在本文中並未提到,不過這對於瞭解dubbo自定義實現SPI的設計已經足夠。