前言
學習之路還是要戒驕戒躁,一以貫之的積累前行。之前的公司部門技術達人少,自己總嚮往那些技術牛人多的團隊,想象自己進去之後能跟別人學到多少東西。如今進到一個這樣的團隊之後,卻發現之前自己的想法過於幼稚。且不說由於人與人之間性格不合導致的難以深入相處,即使相處融洽,別人也不會給你太多的幫扶,更多的還是靠自己去學習去探究。學習的道路上沒有什麼捷徑,且會有很多的心魔需要自己去克服。閒話少敘,今天主要是說一下Dubbo中SPI的基本內容,自適應擴充的部分後面單獨成文。
什麼是SPI
要說Dubbo的SPI,則必須先說說Java原生的SPI。可能很多道友都沒有聽說過SPI,它是Service Provider Interface 即服務提供介面的簡稱,顧名思義,它就是用來提供服務的。
在Java中是如何提供服務的呢?簡要來說,就是在資原始檔目錄下(即resource目錄下)的META-INF/services資料夾下,建立檔名為介面的全路徑名的檔案,檔案內容為此介面的實現類全路徑名。然後在程式碼中通過ServiceLoader類獲取這些配置的實現類,然後就可以自由的使用這麼實現類了。下面是我在本地寫的一個小Demo:
程式碼結構如下所示:
介面程式碼:
1 package spipackage; 2 public interface SpiInterface { 3 void getName(); 4 }
兩個實現類程式碼:
1 package spipackage; 2 public class SpiImpl implements SpiInterface{ 3 @Override 4 public void getName() { 5 System.out.println("SpiImpl"); 6 } 7 }
1 package spipackage; 2 public class SpiImplTwo implements SpiInterface { 3 @Override 4 public void getName() { 5 System.out.println("SpiImplTwo"); 6 } 7 }
資原始檔:
1 spipackage.SpiImpl 2 spipackage.SpiImplTwo
測試類:
1 package spipackage; 2 import java.util.Iterator; 3 import java.util.ServiceLoader; 4 public class SpiTestClient { 5 public static void main(String[] args) { 6 ServiceLoader<SpiInterface> spiInterfaces = ServiceLoader.load(SpiInterface.class); 7 // 迴圈呼叫實現類中的方法 8 spiInterfaces.forEach(SpiInterface::getName); 9 // 獲取某個實現類進行呼叫 10 Iterator<SpiInterface> iterator = spiInterfaces.iterator(); 11 while (iterator.hasNext()) { 12 SpiInterface next = iterator.next(); 13 if (next instanceof SpiImplTwo) { 14 next.getName(); 15 } 16 } 17 } 18 }
測試結果:
什麼是Dubbo的SPI
從java原生SPI的使用上可知,它是一次性載入整個資原始檔中的資料,當你要獲取其中某個實現類時也只能通過遍歷來得到。而Dubbo的開發人員們顯然要讓其更加靈活,所以Dubbo中的SPI是在Java原生SPI基礎上做了改造升級。首先可以按需載入,需要用哪個就載入哪個,這是通過鍵值對來配置實現類做到的,相當於給每個實現類打上了標籤;其次還實現了依賴注入,即如果實現類A中需要注入實現類B,則dubbo在獲取實現類A時會自動將B注入進去。
具體的原生程式碼測試跟上述類似,此處就不在貼出來了,只是需將ServiceLoader換成Dubbo的ExtensionLoader,且介面需帶有@SPI註解,並且資原始檔也可放入META-INF/dubbo目錄下。
下面簡要講一下ExtensionLoader中的原始碼實現。Dubbo的ExtensionLoader類中,獲取服務類的主要方法是getExtension方法,而在這個方法中,核心方法是createExtension,此方法很重要,程式碼如下所示:
1 private T createExtension(String name) { 2 // 1、先獲取class類 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.putIfAbsent(clazz, clazz.newInstance()); 12 instance = (T) EXTENSION_INSTANCES.get(clazz); 13 } 14 // 3、注入依賴,類似spring的依賴注入 15 injectExtension(instance); 16 // 4、將擴充套件物件包進wrapper物件中 17 Set<Class<?>> wrapperClasses = cachedWrapperClasses; 18 if (CollectionUtils.isNotEmpty(wrapperClasses)) { 19 for (Class<?> wrapperClass : wrapperClasses) { 20 instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance)); 21 } 22 } 23 return instance; 24 } catch (Throwable t) { 25 throw new IllegalStateException("Extension instance (name: " + name + ", class: " + 26 type + ") couldn't be instantiated: " + t.getMessage(), t); 27 } 28 }
分四步獲取了類的例項物件。其中第一步中包含了主要的邏輯,它讀取配置檔案,是通過類載入器載入檔案獲取輸入流,然後一行一行讀取的,其中包括了對空格的處理、對註釋的處理。之前總感覺讀取配置檔案的實現很神奇,現在慢慢的可以一窺其中究竟了,覺得也沒多高大上,都是很實際的操作。
小結:SPI的作用
通過SPI實現的功能擴充套件,更類似於插拔式的擴充套件。增加了某些功能類之後,通過配置檔案引入,然後在某些地方獲取,呼叫即可。SPI機制是Dubbo的基礎,瞭解了它才能更加清楚的看清Dubbo的框架設計。另外,通過對SPI的瞭解,個人感覺SPI有點類似於Spring的IOC實現,也可以說Spring通過XMl配置檔案或者註解實現了一種另類的SPI機制,讓你不用關注例項物件的建立,只是用的時候獲取到用即可,當然Spring實現的功能內容更多更易於擴充套件。
只要每天都有進步,都在朝目標前行,就可心安。戒驕戒躁,努力前行!