0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

java本身自帶的SPI擴(kuò)展機(jī)制是怎么一回事?

jf_ro2CN3Fa ? 來源:碼農(nóng)參上 ? 2024-01-02 10:32 ? 次閱讀

八股文背多了,相信大家都聽說過一個詞,SPI 擴(kuò)展。

有的面試官就很喜歡問這個問題,SpringBoot 的自動裝配是如何實(shí)現(xiàn)的?

基本上,你一說是基于 spring 的 SPI 擴(kuò)展機(jī)制,再把spring.factories文件和EnableAutoConfiguration提一下,那么這個問題就答的八九不離十了。

就像四五年前,我去面試的時候被問到這個問題,SPI 動態(tài)擴(kuò)展機(jī)制這幾個詞從嘴里一說出來,就把面試官唬的一愣一愣的??赡芩麄円矝]見過這么能裝逼的,一句話能簡簡單單說明白,非要拽一個聽上去很高大上的詞。

話說回來,被唬住的可不止是面試官,其實(shí)還有我自己。至于 SPI 擴(kuò)展究竟是個啥,是怎么實(shí)現(xiàn)的,我當(dāng)時也根本不明白。

不過現(xiàn)在的面試就是這樣,對線八股文,要想唬住面試官,就得先唬住自己。

那么我們今天暫且不提 spring 的 SPI 擴(kuò)展,先來看看 java 本身自帶的 SPI 擴(kuò)展機(jī)制是怎么一回事。

1、簡介

SPI 的全稱是Service Provider Interface,翻譯過來就是服務(wù)提供者的接口,它所實(shí)現(xiàn)的其實(shí)是一種服務(wù)的發(fā)現(xiàn)機(jī)制。

這么說起來可能還是有點(diǎn)不好理解,我舉個例子來類比一下。

在 spring 項(xiàng)目中,寫 service 層代碼前,會約定俗成的會添加一個接口層。然后通過 spring 中的依賴注入,可以借助@Autowired等方式注入這個接口的實(shí)現(xiàn)類的實(shí)例對象,之后對于 service 的調(diào)用一般也基于接口操作。

簡單形容就是這樣的:

a61bf46c-a6f5-11ee-8b88-92fbcf53809c.jpg

如圖所示,接口、實(shí)現(xiàn)類都是由服務(wù)提供方提供,我們可以把 controller 看作服務(wù)調(diào)用者,調(diào)用方只管調(diào)用接口就可以了。

雖然也有聲音認(rèn)為,大部分情況下 service 只有一個實(shí)現(xiàn)類,接口層顯得有些多余。但是在《Head First Design Patterns》這本書中,大佬們還是建議過:

Program to an interface, not an implementation.

沒錯,就是常說的要面向接口編程。至于好處,也不外乎是降低耦合度、方便日后擴(kuò)展、提高了代碼的靈活性和可維護(hù)性等等。

在上面這個例子里,這個接口層和其中的方法我們可以稱之為API,而我們要討論的SPI和它相比,有類似也有差異,還是先看圖:

a628f11c-a6f5-11ee-8b88-92fbcf53809c.jpg

簡單來說,就是服務(wù)的調(diào)用方定義一個接口規(guī)范,可以由不同的服務(wù)提供者實(shí)現(xiàn)。并且,調(diào)用方能夠通過某種機(jī)制來發(fā)現(xiàn)服務(wù)提供方,并通過接口調(diào)用它的能力。

通過對比,我們可以看出它們雖然都有著接口這一層面,但還是有很大的不同:

API 中的接口是服務(wù)提供者給服務(wù)調(diào)用者的一個功能列表,而 SPI 中更多強(qiáng)調(diào)的是,服務(wù)調(diào)用者對服務(wù)實(shí)現(xiàn)的一種約束,服務(wù)提供者根據(jù)這種約束實(shí)現(xiàn)的服務(wù),可以被服務(wù)調(diào)用者發(fā)現(xiàn)。

說白了,Java 中的 SPI 實(shí)現(xiàn)的就是,你按我的接口規(guī)范實(shí)現(xiàn)服務(wù),我就能通過某種機(jī)制為這個接口尋找到這個服務(wù)。

這么說起來可能還有些抽象,下面我們舉一個例子,類比具體描述一下這個過程。

2、定義接口

說起智能家居系統(tǒng),大家現(xiàn)在都比較熟悉了,只要是相同品牌下的產(chǎn)品,連上 wifi 就能夠通過手機(jī) app 控制了,非常方便。

雖然產(chǎn)品不斷更新?lián)Q代,型號更新層出不窮,但是同種家電在 app 上操作起來,功能一般都是一樣的。就拿空調(diào)來說,我們在 app 上操作起來一般也就三個主要功能:開關(guān)選模式,調(diào)節(jié)溫度。

假設(shè)我現(xiàn)在在客廳、臥室、書房安裝了 3 款不同型號的空調(diào),并把它們都接入到了我 app 中,那么之后的操作都是相同的幾個按鍵,簡單粗暴。

a64497aa-a6f5-11ee-8b88-92fbcf53809c.jpg

思考一下,無論是開關(guān)還是調(diào)溫,都是通過 app 去調(diào)用設(shè)備的接口罷了,那么如果不同型號的空調(diào)各寫各的接口,后端 app 在開發(fā)的時候光對接接口都麻煩的要死。

解決方法也很簡單,我先定義一套接口規(guī)范,不管你以后什么型號的空調(diào),都按我的規(guī)范來實(shí)現(xiàn)接口。以后只要我能發(fā)現(xiàn)你的設(shè)備,那么都可以按相同的方法來調(diào)用接口。

那么下面就先來定義這么一套接口規(guī)范,如果你以后想要接入智能家居系統(tǒng),那么就要遵循這個規(guī)范來開發(fā)接口。

新建一個項(xiàng)目作為標(biāo)準(zhǔn),就叫aircondition-standard好了,然后創(chuàng)建一個接口。除了 3 個操作以外,我們再添加一個獲取空調(diào)型號的方法。

publicinterfaceIAircondition{
//獲取型號
StringgetType();

//開關(guān)
voidturnOnOff();

//調(diào)節(jié)溫度
voidadjustTemperature(inttemperature);

//模式變更
voidchangeModel(intmodelId);
}

這個接口后面要給服務(wù)的實(shí)現(xiàn)方來使用,用 maven 把它打成 jar 包:

mvncleaninstall

之后服務(wù)提供者在項(xiàng)目中就可以引入這個 jar 包了,有了這套規(guī)范,就保證了產(chǎn)品后期不管怎么更新?lián)Q代,都能接入到系統(tǒng)來。

3、服務(wù)實(shí)現(xiàn)

制定并發(fā)布完規(guī)則后,掛式空調(diào)作為第一個服務(wù)提供者就來了,新建一個項(xiàng)目aircondition-hanging-type,并引入剛才打好的 jar 包:


com.cn.hydra
aircondition-standard
1.0-SNAPSHOT

創(chuàng)建服務(wù)類,并實(shí)現(xiàn)前面定義的接口:

publicclassHangingTypeAircondition
implementsIAircondition{
publicStringgetType(){
return"HangingType";
}

publicvoidturnOnOff(){
System.out.println("掛式空調(diào)開關(guān)");
}

publicvoidadjustTemperature(inti){
System.out.println("掛式空調(diào)調(diào)節(jié)溫度");
}

publicvoidchangeModel(inti){
System.out.println("掛式空調(diào)更換模式");
}
}

在項(xiàng)目的resources的目錄下,創(chuàng)建META-INF/services目錄,然后以前面定義的接口名com.cn.hydra.IAircondition創(chuàng)建文件,并在文件中寫入實(shí)現(xiàn)類的全限定名。

com.cn.hydra.HangingTypeAircondition

整個項(xiàng)目結(jié)構(gòu)非常簡單:

a65e6e8c-a6f5-11ee-8b88-92fbcf53809c.png

這樣,一個服務(wù)方的簡單實(shí)現(xiàn)就搞定了,用 maven 打成 jar 包,之后就可以提供給調(diào)用方使用了。

同理,我們可以再創(chuàng)建一個立式空調(diào)的項(xiàng)目aircondition-vertical-type,也只創(chuàng)建一個服務(wù)類:

publicclassVerticalTypeAircondition
implementsIAircondition{
publicStringgetType(){
return"VerticalType";
}

publicvoidturnOnOff(){
System.out.println("立式空調(diào)開關(guān)");
}

publicvoidadjustTemperature(inti){
System.out.println("立式空調(diào)調(diào)節(jié)溫度");
}

publicvoidchangeModel(inti){
System.out.println("立式空調(diào)更換模式");
}
}

還是按上面的命名規(guī)則,創(chuàng)建一個配置文件:

com.cn.hydra.VerticalTypeAircondition

同樣,打成 jar 包就完事了,至于服務(wù)調(diào)用者如何去發(fā)現(xiàn)和調(diào)用這兩個服務(wù),下面詳細(xì)再說。

4、服務(wù)發(fā)現(xiàn)

現(xiàn)在兩個服務(wù)提供方都實(shí)現(xiàn)了接口,下面關(guān)鍵的一步就是服務(wù)發(fā)現(xiàn),這一步 java 中的 spi 發(fā)現(xiàn)機(jī)制已經(jīng)幫我們實(shí)現(xiàn)好了。

創(chuàng)建一個新項(xiàng)目aircondition-app,引入上面打好的兩個 jar 包。



com.cn.hydra
aircondition-hanging-type
1.0-SNAPSHOT



com.cn.hydra
aircondition-vertical-type
1.0-SNAPSHOT


按照上面的說法,雖然每個服務(wù)提供者對于接口都有不同的實(shí)現(xiàn),但是作為調(diào)用者來說,它并不需要關(guān)心具體的實(shí)現(xiàn)類,我們要做的是通過接口來調(diào)用服務(wù)提供者實(shí)現(xiàn)的方法。

下面,就是關(guān)鍵的服務(wù)發(fā)現(xiàn)環(huán)節(jié),我們寫一個方法,根據(jù)型號去調(diào)用對應(yīng)空調(diào)的開關(guān)方法。

publicclassAirconditionApp{
publicstaticvoidmain(String[]args){
newAirconditionApp().turnOn("VerticalType");
}

publicvoidturnOn(Stringtype){
ServiceLoaderload=ServiceLoader
.load(IAircondition.class);

for(IAirconditioniAircondition:load){
System.out.println("檢測到:"+iAircondition.getClass().getSimpleName());
if(type.equals(iAircondition.getType())){
iAircondition.turnOnOff();
}
}
}
}

測試結(jié)果:

a677a3fc-a6f5-11ee-8b88-92fbcf53809c.png

可以看到,測試過程中,通過定義的接口IAircondition發(fā)現(xiàn)了兩個實(shí)現(xiàn)類,并通過參數(shù),調(diào)用了特定實(shí)現(xiàn)類的某個方法。整段代碼中沒有出現(xiàn)過具體的服務(wù)實(shí)現(xiàn)類,操作都是通過接口調(diào)用。

5、原理

了解了 spi 的工作流程,我們再來看看它的實(shí)現(xiàn),其實(shí)最關(guān)鍵的就是上面代碼中出現(xiàn)的ServiceLoader這個類。

上面的示例代碼中,對于ServiceLoader的load()方法的結(jié)果,我們用for循環(huán)進(jìn)行了遍歷,這一點(diǎn)我們看一下源碼就能明白,因?yàn)镾erviceLoader實(shí)現(xiàn)了Iterable這一接口,而整個服務(wù)發(fā)現(xiàn)的核心,就在它的iterator()方法中。

a69198d4-a6f5-11ee-8b88-92fbcf53809c.png

注意這里面有兩個關(guān)鍵的東西,找一下在源碼中定義的地方:

a695696e-a6f5-11ee-8b88-92fbcf53809c.png

注釋寫的非常明白,providers就是一個緩存,在迭代器中如果先從這里面進(jìn)行查找,如果里面有就繼續(xù)往下找,沒有了的話就用這個懶加載的lookupIterator查找。

那么就簡單了,接著往下看LazyIterator,看看它里面的hasNext()和next()兩個方法是怎么實(shí)現(xiàn)的。

a6aba8aa-a6f5-11ee-8b88-92fbcf53809c.png

這個acc是一個安全管理器,在前面通過System.getSecurityManager()判斷并賦值,debug 看一下這里都是null,所以直接看hasNextService()和nextService()方法就可以了。

在hasNextService()方法中,會取出接口取出實(shí)現(xiàn)類的類名放到nextName中:

a6c34690-a6f5-11ee-8b88-92fbcf53809c.png

接下來,在nextService()方法中,則會先加載這個實(shí)現(xiàn)類,然后實(shí)例化對象,最終放入緩存中去。

a6d616c6-a6f5-11ee-8b88-92fbcf53809c.png

在迭代器的迭代過程中,會完成所有實(shí)現(xiàn)類的實(shí)例化,其實(shí)歸根結(jié)底,還是基于 java 反射去實(shí)現(xiàn)的。

6、應(yīng)用

要說 spi 的實(shí)際應(yīng)用,大家最常見的應(yīng)該就是日志框架slf4j了,它利用 spi 實(shí)現(xiàn)了插槽式接入其他具體的日志框架。

說白了,slf4j本身就是個日志門面,并不提供具體的實(shí)現(xiàn),需要綁定其他具體實(shí)現(xiàn)才能真正的引入日志功能。

例如我們可使用log4j2作為具體的綁定器,只需要在 pom 中引入slf4j-log4j12,就可以使用具體功能。


org.slf4j
slf4j-api
2.0.3


org.slf4j
slf4j-log4j12
2.0.3

引入項(xiàng)目后,點(diǎn)開它的 jar 包看一下具體結(jié)構(gòu):

a6ef60d6-a6f5-11ee-8b88-92fbcf53809c.png

有沒有發(fā)現(xiàn)一個彩蛋,先說為什么我們 pom 中引入的明明是slf4j-log4j12,實(shí)際上引入的是slf4j-reload4j?翻一下官網(wǎng)的文檔:

a71e9b76-a6f5-11ee-8b88-92fbcf53809c.png

大意就是在 2015 年和 2022 年,log4j1.x就已經(jīng)宣布end of life終止了,原因也不難猜,估計是因?yàn)轭l繁爆出的漏洞。在那之后,slf4j-log4j在構(gòu)建階段就會自動重定向到slf4j-reload4j了,并且官方也強(qiáng)烈建議使用slf4j-reload4j作為替代。

再回頭看一下 jar 包的META-INF.services里面,通過 spi 注入了Reload4jServiceProvider這個實(shí)現(xiàn)類,它實(shí)現(xiàn)了SLF4JServiceProvider這一接口,在它的初始化方法initialize()中,會完成初始化等工作,后續(xù)可以繼續(xù)獲取到LoggerFactory和Logger等具體日志對象。

7、總結(jié)

Java 中的 SPI 提供了一種比較特別的服務(wù)發(fā)現(xiàn)和調(diào)用機(jī)制,通過接口靈活的將服務(wù)調(diào)用與服務(wù)提供者分離,用于提供給第三方實(shí)現(xiàn)擴(kuò)展時還是很方便的。但是也有缺點(diǎn),比方說一旦加載一個接口,就會把所有實(shí)現(xiàn)類都加載進(jìn)來,可能會加載到不需要的冗余服務(wù)。不過站在整體角度上,還是給我們提供了一種非常不錯的框架擴(kuò)展、集成的思路。

a74cd8a6-a6f5-11ee-8b88-92fbcf53809c.png






審核編輯:劉清

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • JAVA
    +關(guān)注

    關(guān)注

    19

    文章

    2946

    瀏覽量

    104365
  • SPI接口
    +關(guān)注

    關(guān)注

    0

    文章

    258

    瀏覽量

    34288
  • for循環(huán)
    +關(guān)注

    關(guān)注

    0

    文章

    61

    瀏覽量

    2483

原文標(biāo)題:美團(tuán):SPI 的原理是什么?

文章出處:【微信號:芋道源碼,微信公眾號:芋道源碼】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。

收藏 人收藏

    評論

    相關(guān)推薦

    法拉第圓筒是怎么一回事

    法拉第圓筒是怎么一回事?????求詳細(xì)的解說...
    發(fā)表于 07-30 14:40

    請問下數(shù)碼管陽極顯示和共陽極是一回事嗎?

    本帖最后由 eehome 于 2013-1-5 09:43 編輯 請問下數(shù)碼管陽極顯示和共陽極是一回事嗎?
    發(fā)表于 12-03 11:21

    藍(lán)牙中的拓?fù)浣Y(jié)構(gòu)有散射網(wǎng),MESH網(wǎng)是同一回事嗎?

    在藍(lán)牙的學(xué)習(xí)中,有講到藍(lán)牙的拓?fù)浣Y(jié)構(gòu),包含微微網(wǎng)和散射網(wǎng),其中散射網(wǎng)是微微網(wǎng)的拓展網(wǎng)絡(luò)設(shè)備數(shù)量的,請問散射網(wǎng)和MESH網(wǎng)是不是一回事,謝謝
    發(fā)表于 03-15 19:38

    Keil軟件仿真STM32時出現(xiàn)錯誤是怎樣一回事

    Keil軟件仿真STM32時出現(xiàn)錯誤是怎樣一回事?怎樣去解決這個問題?
    發(fā)表于 11-10 06:23

    SFUD庫看不到初始化spi1的GPIO的代碼是怎么一回事

    SFUD庫 看不到初始化spi1的GPIO的代碼是怎么一回事?
    發(fā)表于 07-29 10:39

    請問電源去耦和電源濾波是一回事嗎?

    請問電源去耦和電源濾波是一回事嗎?
    發(fā)表于 04-21 17:42

    請問芯片中ISP Flash和LDROM是不是一回事?

    芯片中 ISP Flash 和 LDROM 是不是一回事? 如果不是一回事,以M453VG6AE為例,它們基地址分別是什么?
    發(fā)表于 08-29 08:08

    超聲波程序中的clock文件和主程序中的Time是怎么一回事?

    超聲波程序中的clock文件和主程序中的Time是怎么一回事?
    發(fā)表于 10-08 07:32

    請問KVA和KW是不是一回事?

    KVA 和KW是不是一回事? 比如負(fù)載時2kw那么我的變壓器的容量需要大于2/cosφ呢?
    發(fā)表于 12-11 07:43

    慢速保險絲是怎樣一回事?

    慢速保險絲是怎樣一回事? 慢速保險絲也叫延時保險絲,它的延時特性表現(xiàn)在電路出現(xiàn)非故障脈沖電流時保持完好而能對長時間的過載提供保護(hù)。有些電路在
    發(fā)表于 11-12 09:11 ?869次閱讀

    機(jī)器人即服務(wù)是怎么一回事

    機(jī)器人即服務(wù)是怎么一回事
    發(fā)表于 08-06 16:48 ?1546次閱讀

    PCB設(shè)計中自動布線和手動布線是怎么一回事?資料下載

    電子發(fā)燒友網(wǎng)為你提供PCB設(shè)計中自動布線和手動布線是怎么一回事?資料下載的電子資料下載,更有其他相關(guān)的電路圖、源代碼、課件教程、中文資料、英文資料、參考設(shè)計、用戶指南、解決方案等資料,希望可以幫助到廣大的電子工程師們。
    發(fā)表于 04-12 08:51 ?14次下載
    PCB設(shè)計中自動布線和手動布線是怎么<b class='flag-5'>一回事</b>?資料下載

    手機(jī)里的射頻與天線是一回事

    手機(jī)里的射頻與天線當(dāng)然不是一回事了。
    的頭像 發(fā)表于 10-04 12:52 ?1w次閱讀

    SMT生產(chǎn)過程中拋料是怎么一回事呢?具體需要怎么解決?

    在SMT工廠,生產(chǎn)過程中經(jīng)常會遇到拋料的情況,甚至有時候拋料會非常嚴(yán)重,影響到生產(chǎn)效率,那么拋料是怎么一回事呢?具體需要怎么解決?
    的頭像 發(fā)表于 01-24 10:42 ?2512次閱讀

    電機(jī)驅(qū)動芯片和電機(jī)控制芯片是一回事

    電機(jī)驅(qū)動芯片和電機(jī)控制芯片在電機(jī)系統(tǒng)中各自扮演著重要的角色,但它們并不是一回事。
    的頭像 發(fā)表于 04-08 11:15 ?1281次閱讀