前言java
學習之路仍是要戒驕戒躁,一以貫之的積累前行。以前的公司部門技術達人少,本身總嚮往那些技術牛人多的團隊,想象本身進去以後能跟別人學到多少東西。現在進到一個這樣的團隊以後,卻發現以前本身的想法過於幼稚。且不說因爲人與人之間性格不合致使的難以深刻相處,即便相處融洽,別人也不會給你太多的幫扶,更多的仍是靠本身去學習去探究。學習的道路上沒有什麼捷徑,且會有不少的心魔須要本身去克服。閒話少敘,今天主要是說一下Dubbo中SPI的基本內容,自適應拓展的部分後面單獨成文。spring
什麼是SPI緩存
要說Dubbo的SPI,則必須先說說Java原生的SPI。可能不少道友都沒有據說過SPI,它是Service Provider Interface 即服務提供接口的簡稱,顧名思義,它就是用來提供服務的。app
在Java中是如何提供服務的呢?簡要來講,就是在資源文件目錄下(即resource目錄下)的META-INF/services文件夾下,創建文件名爲接口的全路徑名的文件,文件內容爲此接口的實現類全路徑名。而後在代碼中經過ServiceLoader類獲取這些配置的實現類,而後就能夠自由的使用這麼實現類了。下面是我在本地寫的一個小Demo:框架
代碼結構以下所示:ide
接口代碼:學習
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 }
複製代碼
資源文件:.net
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 // 一、先獲取class類
3 Class<?> clazz = getExtensionClasses(www.zzhehong.com).get(name);
4 if (clazz == null)www.shengyaoyL1.com {
5 throw findException(name);
6 }
7 try {
8 T instance = (T) EXTENSION_INSTANCES.get(www.zheshengyuLe.com clazz);
9 if (instance == null) {
10 // 二、經過反射建立實例,且存入緩存
11 EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
12 instance = (www.zeshengyuLe.com) EXTENSION_INSTANCES.get(clazz);
13 }
14 // 三、注入依賴,相似spring的依賴注入
15 injectExtension(instance);
16 // 四、將擴展對象包進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(www.oushengyule.com"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實現的功能內容更多更易於擴展。
只要天天都有進步,都在朝目標前行,就可心安。戒驕戒躁,努力前行!