前言:開閉原則一直是軟件開發領域中所追求的,開閉原則中的"開"是指對於組件功能的擴展是開放的,是容許對其進行功能擴展的,「閉」,是指對於原有代碼的修改是封閉的,即不該該修改原有的代碼。對於一個高度集成化的、成熟、穩健的系統來說,永遠不是封閉、固守的,它須要向外提供必定的可擴展的能力,外部的實現類或者jar包均可以調用它。在面向對象的開發領域中,接口是對系統功能的高度抽象,由於SPI可謂是"應運而生",本篇博客就開始走進SPI,探究java自身的SPI和Dubbo的SPI究竟是什麼原理java
目錄mysql
一:SPI是什麼sql
二:jdk的SPI數據庫
三:dubbo的SPI緩存
四:總結安全
正文oracle
一:SPI是什麼?app
spi全稱英文是service provider Interface,翻譯成中文也就是服務提供接口,在jdk 1.6開始,就已經提供了SPI.它的使用比較簡單。即在項目的類路徑下提供一個META/services/xx文件,配置一個文件,文件名爲接口的全路徑的名稱,內容爲具體的實現類全路徑名。jdk將會使用ServiceLoader.load()方法去解析和加載接口和其中的實現類,按需執行不一樣的方法。負載均衡
舉個簡單的例子:在jdbc中,jdk提供了driver(數據庫)接口,可是不一樣的廠商實現起來的方式不一樣,好比mysql、oracle、sqlLite等廠商底層的實現邏輯都是不一樣的,所以在對數據庫驅動driver實現方式上,能夠採用SPI機制。好比在mysql-contactor.jar包中會在META/services路徑下,這裏至關於擴展了java.sql.Driver接口,jdk會在META/services路徑下掃描該文件,而後加載mysql的diver實現類com.mysql.cj.jdbc.Driver,就至關於擴展了Driver的接口能力,按需加載mysql的實現類。oracle的鏈接jar包會有oracle的配置文件,這樣不一樣的數據庫根據自身的不一樣邏輯按需擴展了Driver的能力,這就是SPI的最大好處。框架
java.sql.Driver的內容:
二:java的SPI機制
從jdk1.6開始,java就提供了spi機制的支持,接下來咱們就從一個例子來講明jdk的spi是如何實現的?
2.1:設計一個接口
public interface Animal { void sound(); }
2.2:有兩個實現類:
public class Cat implements Animal { public void sound() { System.out.println("小貓在叫"); } }
public class Dog implements Animal { public void sound() { System.out.println("小狗在叫"); } }
2.3:配置META-INF類
2.4:讀取配置
public static void main(String[] args) { ServiceLoader<Animal> serviceLoader = ServiceLoader.load(Animal.class); final Iterator<Animal> iterator = serviceLoader.iterator(); while (iterator.hasNext()) { Animal next = iterator.next(); next.sound(); } } }
2.5:原理
在上面的例子中:定義了一個接口Animal,而後有兩個實現類:Cat和Dog,在META-INF的文件目錄下,兩個接口都進行了相關的配置,接口實現類模塊要同時加載兩個類,具體的調用邏輯在客戶端的ServiceLoader中來經過迭代器遍從來調用具體的配置實現類,那麼代碼具體的原理是什麼呢?跟着我一塊兒走進源碼來分析一下jdk:
在ServiceLoader的load方法中首先會獲取上下文類加載器,而後構造一個ServiceLoader,在ServiceLoader中有一個懶加載器,懶加載器會經過BufferedReader來從META-INF/services路徑下讀取對應的接口名的全路徑名文件,也就是咱們配置的文件,而後經過文件的類解析器讀取文件中的內容,再經過類加載器加載類的全路徑
仔細分析下java的spi具備如下缺點:
①沒法按需加載。雖然 ServiceLoader 作了延遲載入,使用了LazyIterator,可是基本只能經過遍歷所有獲取,接口的實現類得所有載入並實例化一遍。若是你並不想用某些實現類,或者某些類實例化很耗時,它也被載入並實例化了,假如我只須要其中一個,其它的並不須要這就造成了必定的資源消耗浪費
②不具備IOC的功能,假如我有一個實現類,如何將它注入到個人容器中呢,類之間依賴關係如何完成呢?
③serviceLoader不是線程安全的,會出現線程安全的問題
dubbo在原有的spi基礎上主要有如下的改變,①配置文件採用鍵值對配置的方式,使用起來更加靈活和簡單 ② 加強了本來SPI的功能,使得SPI具有ioc和aop的功能,這在本來的java中spi是不支持的。dubbo的spi是經過ExtensionLoader來解析的,經過ExtensionLoader來加載指定的實現類,配置文件的路徑在META-INF/dubbo路徑下,咱們經過一個例子來了解dubbo的SPI運行機制:
3.1:dubbo的負載均衡機制其中就採用了spi機制,選擇哪一個負載均衡策略是經過@SPI註解來實現的:
利用ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(name)來獲取具體的LoadBalance的實現類,其中name是對應配置文件(見下文)中的鍵;
其中用了@SPI來指定了dubbo的負載均衡策略爲隨機(random),咱們再來了解一下@SPI註解和@Adaptive是如何工做的?
3.2:在dubbo的META_INF.dubbo.internal路徑下存在一個文件:
com.alibaba.dubbo.rpc.cluster.LoadBalance文件,文件內容是這樣的:
能夠看出dubbo的spi配置是採用鍵值對的方式,鍵值對最大的好處就是能夠以鍵來獲取值,取值比較簡單和方便。這點和java的spi配置方式是不一樣的,java的spi只有全路徑名;
Dubbo經過註解@Adaptive做爲標記實現了一個適配器類,dubbo將會爲這個類動態生成代理對象;ExtensionLoader中獲取默認實現類或者經過實現類名稱(由@SPI註解指定的名稱)來獲取實現類
爲何會出現@Adaptive這個註解呢?主要緣由是由於dubbo的加載擴展了是從配置文件加載的,是很動態的,可是實現類卻要固定寫死或者靈活實現,因此就得區分開。用@Adaptive就是表示由框架本身生成,不須要人爲實現.
在dubbo加載SPI時會動態建立SPI Adaptive
實現ExtensionLoader
。從URL獲取密鑰,該密鑰將經過@Adaptive由
接口方法定義的註釋提供
3.4.1:從解析加載配置類的源碼開始分析
ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(Constants.DEFAULT_LOADBALANCE)
上面的源碼比較簡單,首先是根據傳入的接口從緩存(一個以class爲鍵,ExtensionLoader爲值的concurrentHashMap)中獲取,若是拿不到就放入到緩存中;邏輯比較簡單,這裏就不作詳細分析了。接下來主要是分析:ExtensionLoader.getExtension(name)
咱們來看下具體的createExtension方法的源碼:
createExtension 方法的邏輯稍複雜一下,包含了以下的步驟:
①經過 getExtensionClasses 獲取全部的拓展類,也就是全部META-INF下的配置文件中的鍵值對名
②經過反射建立拓展對象
③向拓展對象中注入依賴
④將拓展對象包裹在相應的 Wrapper 對象中,後面須要從wrapper中取
來具體看一下dubbo是如何解析配置文件的:
上面能夠看出三個路徑,這和咱們剛纔上面看到的路徑是一致的,dubbo就是讀取該路徑下的的文件
加載配置文件下的文件內容,也就是上面的com.alibaba.dubbo.rpc.cluster.LoadBalance文件
dubbo的IOC是經過setter方法來實現注入的,經過遍歷對象實例的全部方法,找到其setter方法在進行截取,從objectFactory中獲取擴展類再進行反射執行。這樣的話,就算實現實例中有依賴的擴展實例,均可以注入完成,是dubbo的IOC體現。ojectFactory 變量的類型爲 AdaptiveExtensionFactory,AdaptiveExtensionFactory 內部維護了一個 ExtensionFactory 列表,用於存儲其餘類型的 ExtensionFactory。
本篇博客簡單分別介紹了 Java SPI 與 Dubbo SPI 用法,java的spi舉了個簡單的例子來進行了說明。並仔細分析了jdk的spi不足,dubbo是如何面對jdk的不足之處,而後本身定製開發出一套更加合理和更好的dubbo自我實現。以及詳細分析了 Dubbo SPI 的加載拓展類的過程和源碼的分析。其中能夠看出來dubbo中對於緩存和反射的利用是至關之多的.SPI是軟件設計中高擴展性的一個體現,經過spi機制能夠靈活地實現廠商的規範訂製和不一樣企業的具體規範本身實現.高度擴展了原程序,使得咱們設計出來的程序更加具備擴展力。