Dubbo源碼學習之-SPI介紹


前言

學習之路還是要戒驕戒躁,一以貫之的積累前行。之前的公司部門技術達人少,自己總向往那些技術牛人多的團隊,想象自己進去之后能跟別人學到多少東西。如今進到一個這樣的團隊之后,卻發現之前自己的想法過於幼稚。且不說由於人與人之間性格不合導致的難以深入相處,即使相處融洽,別人也不會給你太多的幫扶,更多的還是靠自己去學習去探究。學習的道路上沒有什么捷徑,且會有很多的心魔需要自己去克服。閑話少敘,今天主要是說一下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實現的功能內容更多更易於擴展。

只要每天都有進步,都在朝目標前行,就可心安。戒驕戒躁,努力前行!

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM