Java SPI
SPI全稱Service Provider Interface,是Java提供的一套用來被第三方實(shí)現(xiàn)或者擴(kuò)展的接口,它可以用來啟用框架擴(kuò)展和替換組件。 SPI的作用就是為這些被擴(kuò)展的API尋找服務(wù)實(shí)現(xiàn)。
原因
面向的對(duì)象的設(shè)計(jì)里,我們一般推薦模塊之間基于接口編程,模塊之間不對(duì)實(shí)現(xiàn)類進(jìn)行硬編碼。一旦代碼里涉及具體的實(shí)現(xiàn)類,就違反了可拔插的原則,如果需要替換一種實(shí)現(xiàn),就需要修改代碼。為了實(shí)現(xiàn)在模塊裝配的時(shí)候不用在程序里動(dòng)態(tài)指明,這就需要一種服務(wù)發(fā)現(xiàn)機(jī)制。java spi就是提供這樣的一個(gè)機(jī)制:為某個(gè)接口尋找服務(wù)實(shí)現(xiàn)的機(jī)制。這有點(diǎn)類似IOC的思想,將裝配的控制權(quán)移到了程序之外。
API SPI
API (Application Programming Interface)在大多數(shù)情況下,都是實(shí)現(xiàn)方制定接口并完成對(duì)接口的實(shí)現(xiàn),調(diào)用方僅僅依賴接口調(diào)用,且無權(quán)選擇不同實(shí)現(xiàn)。 從使用人員上來說,API 直接被應(yīng)用開發(fā)人員使用。
SPI (Service Provider Interface)是調(diào)用方來制定接口規(guī)范,提供給外部來實(shí)現(xiàn),調(diào)用方在調(diào)用時(shí)則選擇自己需要的外部實(shí)現(xiàn)。 從使用人員上來說,SPI 被框架擴(kuò)展人員使用。
實(shí)現(xiàn)
- 定義接口
- 編寫實(shí)現(xiàn)類
- 在resources目錄下新建META-INF/services目錄,并且在這個(gè)目錄下新建一個(gè)與上述接口的全限定名一致的文件,在這個(gè)文件中寫入接口的實(shí)現(xiàn)類的全限定名
比如接口全限定名:com.spi.cache.Cache
文件內(nèi)容:
com.spi.cache.LocalCache
com.spi.cache.RedisCache - 最后調(diào)用ServiceLoader.load(UploadCDN.class)即可加載出以上兩個(gè)實(shí)現(xiàn)類
原理
Class.forName反射加載并實(shí)例化進(jìn)緩存
缺點(diǎn)
Java SPI 在查找擴(kuò)展實(shí)現(xiàn)類的時(shí)候遍歷 SPI 的配置文件并且將實(shí)現(xiàn)類全部實(shí)例化,無法按需加載
Dubbo SPI

優(yōu)點(diǎn)
Dubbo 的擴(kuò)展點(diǎn)加載是基于JDK 標(biāo)準(zhǔn)的 SPI 擴(kuò)展點(diǎn)發(fā)現(xiàn)機(jī)制增強(qiáng)而來的,Dubbo 改進(jìn)了 JDK 標(biāo)準(zhǔn)的 SPI 的以下問題:
JDK 標(biāo)準(zhǔn)的 SPI 會(huì)一次性實(shí)例化擴(kuò)展點(diǎn)所有實(shí)現(xiàn),如果有擴(kuò)展實(shí)現(xiàn)初始化很耗時(shí),但如果沒用上也加載,會(huì)很浪費(fèi)資源。
如果擴(kuò)展點(diǎn)加載失敗,就失敗了,給用戶沒有任何通知。比如:JDK 標(biāo)準(zhǔn)的ScriptEngine,如果Ruby ScriptEngine 因?yàn)樗蕾嚨?jruby.jar 不存在,導(dǎo)致 Ruby ScriptEngine 類加載失敗,這個(gè)失敗原因被吃掉了,當(dāng)用戶執(zhí)行 ruby 腳本時(shí),會(huì)報(bào)空指針異常,而不是報(bào)Ruby ScriptEngine不存在。
增加了對(duì)擴(kuò)展點(diǎn) IoC 和 AOP 的支持,一個(gè)擴(kuò)展點(diǎn)可以直接 setter 注入其它擴(kuò)展點(diǎn)。
配置文件
文件名同樣是接口全限定名,而內(nèi)容是k-v形式,比如localCache=com.spi.cache.LocalCache,同樣這樣的方式實(shí)現(xiàn)按需加載
注解
@SPI注解(注解在類上)
標(biāo)識(shí)了接口是一個(gè)擴(kuò)展點(diǎn),屬性 value 用來指定默認(rèn)適配擴(kuò)展點(diǎn)的名稱。
@Adaptive注解(注解在類型和方法上)
注解在類上 , 這個(gè)類就是缺省的適配擴(kuò)展;注解在擴(kuò)展點(diǎn) @SPI 的方法上時(shí) , dubbo 動(dòng)態(tài)的生成一個(gè)這個(gè)擴(kuò)展點(diǎn)的適配擴(kuò)展類
@Activate注解 - 自適應(yīng)擴(kuò)展(注解在類型和方法上)
注解在擴(kuò)展點(diǎn)的實(shí)現(xiàn)類上 ,表示了一個(gè)擴(kuò)展類被獲取到的的條件,根據(jù) @Activate 中的 group 、 value 屬性來過濾,
文件目錄
- META-INF/services/ 目錄:該目錄下的 SPI 配置文件是為了用來兼容 Java SPI 。
- META-INF/dubbo/ 目錄:該目錄存放用戶自定義的 SPI 配置文件。
- META-INF/dubbo/internal/ 目錄:該目錄存放 Dubbo 內(nèi)部使用的 SPI 配置文件。
AOP
https://www.cnblogs.com/UYGHYTYH/p/13028591.html

利用符合構(gòu)造函數(shù)要求的裝飾者模式包裝類來包裝實(shí)現(xiàn)類
IOC
一個(gè)擴(kuò)展點(diǎn)可以通過 setter 注入其它擴(kuò)展點(diǎn)
原理
ExtensionLoader類似 Java SPI 中 ServiceLoader
- getExtension():主要用于獲取名稱為name的對(duì)應(yīng)的子類的對(duì)象,這里如果子類對(duì)象如果有AOP相關(guān)的配置,這里也會(huì)對(duì)其進(jìn)行封裝;
- getAdaptiveExtension():使用定義的裝飾類來封裝目標(biāo)子類,具體使用哪個(gè)子類可以在定義的裝飾類中通過一定的條件進(jìn)行配置;
- getExtensionLoader():加載當(dāng)前接口的子類并且實(shí)例化一個(gè)ExtensionLoader對(duì)象。
使用:ExtensionLoader.getExtensionLoader(Cache.class).getExtension()或其他方法