閱讀本文須要具有java spi的基礎,本文不講java spi,please google it.html
SPI(Service Provider Interface)是服務發現機制,Dubbo沒有使用jdk SPI而對其加強和擴展:java
你能夠發現Dubbo的源碼中有不少地方都用到了@SPI註解,例如:Protocol(通訊協議),LoadBalance(負載均衡)等。基於Dubbo SPI,咱們能夠很是容易的進行拓展。ExtensionLoader是擴展點核心類,用於載入Dubbo中各類可配置的組件,好比剛剛說的Protocol和LoadBalance等。那麼接下來咱們看一下Dubbo SPI的示例apache
好比如今咱們要拓展Protocol這個組件,新建一個DefineProtocol類並修改默認端口爲8888:api
1 /** 2 * @author GrimMjx 3 */ 4 public class DefineProtocol implements Protocol { 5 @Override 6 public int getDefaultPort() { 7 return 8888; 8 } 9 10 @Override 11 public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException { 12 return null; 13 } 14 15 @Override 16 public <T> Invoker<T> refer(Class<T> aClass, URL url) throws RpcException { 17 return null; 18 } 19 20 @Override 21 public void destroy() { 22 23 } 24 }
配置文件的文件名字是接口的全限定名,那麼在這個例子中就是:com.alibaba.dubbo.rpc.Protocol緩存
Dubbo SPI所需的配置文件要放在如下3個目錄任意一箇中:app
META-INF/services/負載均衡
META-INF/dubbo/框架
META-INF/dubbo/internal/ide
同時須要將服務提供者配置文件設計成KV鍵值對的形式,Key是拓展類的name,Value是擴展的全限定名實現類。好比:源碼分析
myProtocol=com.grimmjx.edu.DefineProtocol
而後測試一下:
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("dubbo-client.xml"); Protocol myProtocol = ExtensionLoader.getExtensionLoader(Protocol.class).getExtension("myProtocol"); System.out.println(myProtocol.getDefaultPort());
結果以下:
那咱們就從上面的方法看起,重要方法紅色標註:
ExtensionLoader.getExtensionLoader(Protocol.class).getExtension("myProtocol");
1.getExtensionLoader方法,入參是一個可拓展的藉口,返回ExtensionLoader實體類,而後能夠經過name(key)來獲取具體的擴展:
1 public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) { 2 if (type == null) { 3 throw new IllegalArgumentException("Extension type == null"); 4 } 5 // 擴展點必須是接口 6 if (!type.isInterface()) { 7 throw new IllegalArgumentException("Extension type (" + type + ") is not an interface!"); 8 } 9 // 必須有@SPI註解 10 if (!withExtensionAnnotation(type)) { 11 throw new IllegalArgumentException("Extension type (" + type + 12 ") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!"); 13 } 14 // 每一個擴展只會被加載一次 15 ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type); 16 if (loader == null) { 17 // 初始化擴展 18 EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type)); 19 loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type); 20 } 21 return loader; 22 }
2.getExtension方法,首先檢查緩存,若是沒有則用雙檢鎖方式建立實例:
1 public T getExtension(String name) { 2 if (StringUtils.isEmpty(name)) { 3 throw new IllegalArgumentException("Extension name == null"); 4 } 5 if ("true".equals(name)) { 6 // 默認拓展實現類 7 return getDefaultExtension(); 8 } 9 // 獲取持有目標對象 10 Holder<Object> holder = getOrCreateHolder(name); 11 Object instance = holder.get(); 12 // 雙檢鎖 13 if (instance == null) { 14 synchronized (holder) { 15 instance = holder.get(); 16 if (instance == null) { 17 // 建立實例 18 instance = createExtension(name); 19 holder.set(instance); 20 } 21 } 22 } 23 return (T) instance; 24 }
3.createExtension方法,這個方法比較核心。作了有4件事情,第3件和第4件分別爲上面介紹Dubbo SPI中對jdk SPI擴展的第二和第三點(紅字已標註)。請看代碼註釋:
1 private T createExtension(String name) { 2 // 1.加載配置文件全部拓展類,獲得配置名-拓展類的map,從map中獲取到拓展類 3 Class<?> clazz = getExtensionClasses().get(name); 4 if (clazz == null) { 5 throw findException(name); 6 } 7 try { 8 T instance = (T) EXTENSION_INSTANCES.get(clazz); 9 if (instance == null) { 10 // 2.經過反射建立實例 11 // EXTENSION_INSTANCES這個map是配置名-拓展類實例的map 12 EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance()); 13 instance = (T) EXTENSION_INSTANCES.get(clazz); 14 } 15 // 3.注入依賴,即IOC 16 injectExtension(instance); 17 Set<Class<?>> wrapperClasses = cachedWrapperClasses; 18 if (CollectionUtils.isNotEmpty(wrapperClasses)) { 19 // 4.循環建立Wrapper實例 20 for (Class<?> wrapperClass : wrapperClasses) { 21 // 經過反射建立Wrapper實例 22 // 向Wrapper實例注入依賴,最後賦值給instance 23 // 自動包裝實現相似aop功能 24 instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance)); 25 } 26 } 27 return instance; 28 } catch (Throwable t) { 29 throw new IllegalStateException("Extension instance (name: " + name + ", class: " + 30 type + ") couldn't be instantiated: " + t.getMessage(), t); 31 } 32 }
4.getExtensionClasses方法,這裏就是找出全部拓展類,返回一個配置名-拓展類的map。
1 private Map<String, Class<?>> getExtensionClasses() { 2 Map<String, Class<?>> classes = cachedClasses.get(); 3 // 雙檢鎖 4 if (classes == null) { 5 synchronized (cachedClasses) { 6 classes = cachedClasses.get(); 7 if (classes == null) { 8 // 緩存無則加載 9 classes = loadExtensionClasses(); 10 cachedClasses.set(classes); 11 } 12 } 13 } 14 return classes; 15 }
5.loadExtensionClasses方法,主要就是解析SPI註解,而後加載指定目錄的配置文件,也不是很難
1 private Map<String, Class<?>> loadExtensionClasses() { 2 // 獲取SPI註解,檢查合法等 3 final SPI defaultAnnotation = type.getAnnotation(SPI.class); 4 if(defaultAnnotation != null) { 5 String value = defaultAnnotation.value(); 6 if(value != null && (value = value.trim()).length() > 0) { 7 String[] names = NAME_SEPARATOR.split(value); 8 if(names.length > 1) { 9 throw new IllegalStateException("more than 1 default extension name on extension " + type.getName() 10 + ": " + Arrays.toString(names)); 11 } 12 if(names.length == 1) cachedDefaultName = names[0]; 13 } 14 } 15 16 Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>(); 17 // META-INF/dubbo/internal/目錄 18 loadFile(extensionClasses, DUBBO_INTERNAL_DIRECTORY); 19 // META-INF/dubbo/目錄 20 loadFile(extensionClasses, DUBBO_DIRECTORY); 21 // META-INF/services/目錄 22 loadFile(extensionClasses, SERVICES_DIRECTORY); 23 return extensionClasses; 24 }
這裏返回的extensionClasses的map就確定包含了"myProtocol"->"com.grimmjx.edu.DefineProtocol"。同時也能夠看到,dubbo支持有不少協議:
接下來不用多說了吧,再從map裏get出"myProtocol",獲得的就是咱們自定義的協議類。
上面3.createExtension方法,這個方法裏註釋的3和4很關鍵,裏面實現了依賴注入和AOP的功能,那麼接下來咱們主要看看Dubbo的IOC和AOP
Dubbo IOC是經過setter方法注入依賴的,首先遍歷方法是否有setter方法特徵,若是有則經過objectFactory獲取依賴對象進行注入。Dubbo注入的多是Dubbo的擴展,也有多是一個Spring bean!
上面的3方法中有這一行代碼,實現了Dubbo SPI的IOC
injectExtension(instance);
1.injectExtension方法。咱們主要看這個方法,自動裝配的功能都在這個方法中:
1 private T injectExtension(T instance) { 2 try { 3 if (objectFactory != null) { 4 for (Method method : instance.getClass().getMethods()) { 5 // 若是方法以set開頭 && 只有一個參數 && 方法是public級別的 6 if (method.getName().startsWith("set") 7 && method.getParameterTypes().length == 1 8 && Modifier.isPublic(method.getModifiers())) { 9 // 獲取setter方法參數類型 10 Class<?> pt = method.getParameterTypes()[0]; 11 try { 12 String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : ""; 13 14 // 關鍵,從objectFactory裏拿出依賴對象 15 Object object = objectFactory.getExtension(pt, property); 16 if (object != null) { 17 // 利用反射進行注入 18 method.invoke(instance, object); 19 } 20 } catch (Exception e) { 21 logger.error("fail to inject via method " + method.getName() 22 + " of interface " + type.getName() + ": " + e.getMessage(), e); 23 } 24 } 25 } 26 } 27 } catch (Exception e) { 28 logger.error(e.getMessage(), e); 29 } 30 return instance; 31 }
2.objectFactory,究竟這裏的objectFactory是什麼呢?它是ExtensionFactory類型的,自身也是一個擴展點。我先告訴你這裏的objectFactory是AdaptiveExtensionFactory。後面會有解釋。
1 private ExtensionLoader(Class<?> type) { 2 this.type = type; 3 objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension()); 4 }
3.getExtension方法,這裏比較簡單,爲了直觀,用debug模式看一下,factories有兩個,分別是SpiExtensionFactory和SpringExtensionFactory
對於SpringExtensionFactory就是從工廠里根據beanName拿到Spring bean來注入,對於SpiExtensionFactory就是根據傳入Class獲取自適應拓展類,那麼咱們寫一段代碼,來試試獲取一個Spring Bean玩玩,先定義一個bean:
1 /** 2 * @author GrimMjx 3 */ 4 public class Worker { 5 6 private int age = 24; 7 8 public int getAge() { 9 return age; 10 } 11 12 public void setAge(int age) { 13 this.age = age; 14 } 15 }
而後再配置文件裏配置這個Spring Bean:
最後簡單寫個Main方法,能夠看出SpringExtensionFactory能夠加載Spring Bean:
Dubbo中也支持Spring AOP相似功能,經過裝飾者模式,使用包裝類包裝原始的擴展點實例。在擴展點實現先後插入其餘邏輯,實現AOP功能。說這很繞口啊,那什麼是包裝類呢?舉個例子你就知道了:
1 class A{ 2 private A a; 3 public A(A a){ 4 this.a = a; 5 } 6 7 public void do(){ 8 // 插入擴展邏輯 9 a.do(); 10 } 11 }
這裏的插入擴展邏輯,是否是就是實現了AOP功能呢?好比說Protocol類,有2個Wrapper,分別是ProtocolFilterWrapper和ProtocolListenerWrapper,咱們能夠在dubbo-rpc/dubbo-rpc-api/src/main/resources/META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Protocol看到:
filter=com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper
listener=com.alibaba.dubbo.rpc.protocol.ProtocolListenerWrapper
源碼的話createExtension方法裏的註釋已經寫的很清楚了,這裏能夠自行研究。因此咱們在最開始的Dubbo SPI的例子中,咱們打個斷點就很明顯了,獲得的myProtocol對象實際上是這樣的:
若是你調用export方法的話,會先經歷ProtocolFilterWrapper的export方法,再經歷ProtocolListenerWrapper的export方法,這樣是否是就實現了Spring AOP的功能呢?
這裏getAdaptiveExtension到底獲取的是什麼呢,這裏涉及到SPI 自適應擴展,十分重要,涉及到@Adaptive註解。
若是註解加在類上,好比說com.alibaba.dubbo.common.extension.factory.AdaptiveExtensionFactory(自行驗證):直接加載當前的適配器
若是註解加載方法上,好比說com.alibaba.dubbo.rpc.Protocol:動態建立一個自適應的適配器,就像是執行以下代碼,返回的是一個動態生成的代理類
Protocol adaptiveExtension = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
1 public class Protocol$Adpative implements com.alibaba.dubbo.rpc.Protocol { 2 public void destroy() { 3 throw new UnsupportedOperationException("method public abstract void com.alibaba.dubbo.rpc.Protocol.destroy() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!"); 4 } 5 6 public int getDefaultPort() { 7 throw new UnsupportedOperationException("method public abstract int com.alibaba.dubbo.rpc.Protocol.getDefaultPort() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!"); 8 } 9 10 public com.alibaba.dubbo.rpc.Exporter export(com.alibaba.dubbo.rpc.Invoker arg0) { 11 if (arg0 == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null"); 12 if (arg0.getUrl() == null) 13 throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument getUrl() == null"); 14 com.alibaba.dubbo.common.URL url = arg0.getUrl(); 15 String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol()); 16 if (extName == null) 17 throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])"); 18 com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName); 19 return extension.export(arg0); 20 } 21 22 public com.alibaba.dubbo.rpc.Invoker refer(java.lang.Class arg0, com.alibaba.dubbo.common.URL arg1) { 23 if (arg1 == null) throw new IllegalArgumentException("url == null"); 24 com.alibaba.dubbo.common.URL url = arg1; 25 String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol()); 26 if (extName == null) 27 throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])"); 28 com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName); 29 return extension.refer(arg0, arg1); 30 } 31 }
爲何getDefaultPort和destroy方法都是直接拋出異常呢?由於Protocol接口只有export和refer方法使用了@Adaptive註解,Dubbo會自動生成自適應實例,其餘方法都是拋異常。
爲何還要要動態生成呢?有時候拓展不像在框架啓動的時候被加載,而是但願在擴展方法被調用的時候,根據運行時參數進行加載。
最後看看上面2個標紅的代碼,是否是就是本文開始的源碼分析,是否是很簡單了?
http://dubbo.apache.org/zh-cn/docs/source_code_guide/dubbo-spi.html(官方文檔,穩穩的)