精品国产人成在线_亚洲高清无码在线观看_国产在线视频国产永久2021_国产AV综合第一页一个的一区免费影院黑人_最近中文字幕MV高清在线视频

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

基于spring的SPI擴展機制是如何實現的?

jf_ro2CN3Fa ? 來源:碼農參上 ? 2023-03-07 09:17 ? 次閱讀

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

有的面試官就很喜歡問這個問題,SpringBoot 的自動裝配是如何實現的?

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

就像四五年前,我去面試的時候被問到這個問題,SPI 動態擴展機制 這幾個詞從嘴里一說出來,就把面試官唬的一愣一愣的。可能他們也沒見過這么能裝逼的,一句話能簡簡單單說明白,非要拽一個聽上去很高大上的詞。

話說回來,被唬住的可不止是面試官,其實還有我自己。至于 SPI 擴展究竟是個啥,是怎么實現的,我當時也根本不明白。

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

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

1、簡介

SPI 的全稱是Service Provider Interface,翻譯過來就是服務提供者的接口 ,它所實現的其實是一種服務的發現機制。

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

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

簡單形容就是這樣的:

d8448642-bc7d-11ed-bfe3-dac502259ad0.jpg

如圖所示,接口、實現類都是由服務提供方提供,我們可以把 controller 看作服務調用者,調用方只管調用接口就可以了。

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

Program to an interface, not an implementation.

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

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

d8603fd6-bc7d-11ed-bfe3-dac502259ad0.jpg

簡單來說,就是服務的調用方定義一個接口規范,可以由不同的服務提供者實現。并且,調用方能夠通過某種機制來發現服務提供方,并通過接口調用它的能力。

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

API 中的接口是服務提供者給服務調用者的一個功能列表,而 SPI 中更多強調的是,服務調用者對服務實現的一種約束,服務提供者根據這種約束實現的服務,可以被服務調用者發現。

說白了,Java 中的 SPI 實現的就是,你按我的接口規范實現服務,我就能通過某種機制為這個接口尋找到這個服務。

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

2、定義接口

說起智能家居系統,大家現在都比較熟悉了,只要是相同品牌下的產品,連上 wifi 就能夠通過手機 app 控制了,非常方便。

雖然產品不斷更新換代,型號更新層出不窮,但是同種家電在 app 上操作起來,功能一般都是一樣的。就拿空調來說,我們在 app 上操作起來一般也就三個主要功能:開關選模式調節溫度

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

d872a176-bc7d-11ed-bfe3-dac502259ad0.jpg

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

解決方法也很簡單,我先定義一套接口規范,不管你以后什么型號的空調,都按我的規范來實現接口。以后只要我能發現你的設備,那么都可以按相同的方法來調用接口。

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

新建一個項目作為標準,就叫aircondition-standard好了,然后創建一個接口。除了 3 個操作以外,我們再添加一個獲取空調型號的方法。

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

//開關
voidturnOnOff();

//調節溫度
voidadjustTemperature(inttemperature);

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

這個接口后面要給服務的實現方來使用,用 maven 把它打成 jar 包:

mvncleaninstall

之后服務提供者在項目中就可以引入這個 jar 包了,有了這套規范,就保證了產品后期不管怎么更新換代,都能接入到系統來。

3、服務實現

制定并發布完規則后,掛式空調 作為第一個服務提供者就來了,新建一個項目aircondition-hanging-type,并引入剛才打好的 jar 包:


com.cn.hydra
aircondition-standard
1.0-SNAPSHOT

創建服務類,并實現前面定義的接口:

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

publicvoidturnOnOff(){
System.out.println("掛式空調開關");
}

publicvoidadjustTemperature(inti){
System.out.println("掛式空調調節溫度");
}

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

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

com.cn.hydra.HangingTypeAircondition

整個項目結構非常簡單:

d8864118-bc7d-11ed-bfe3-dac502259ad0.png

這樣,一個服務方的簡單實現就搞定了,用 maven 打成 jar 包,之后就可以提供給調用方使用了。

同理,我們可以再創建一個立式空調 的項目aircondition-vertical-type,也只創建一個服務類:

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

publicvoidturnOnOff(){
System.out.println("立式空調開關");
}

publicvoidadjustTemperature(inti){
System.out.println("立式空調調節溫度");
}

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

還是按上面的命名規則,創建一個配置文件:

com.cn.hydra.VerticalTypeAircondition

同樣,打成 jar 包就完事了,至于服務調用者如何去發現和調用這兩個服務,下面詳細再說。

4、服務發現

現在兩個服務提供方都實現了接口,下面關鍵的一步就是服務發現,這一步 java 中的 spi 發現機制已經幫我們實現好了。

創建一個新項目aircondition-app,引入上面打好的兩個 jar 包。



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



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


按照上面的說法,雖然每個服務提供者對于接口都有不同的實現,但是作為調用者來說,它并不需要關心具體的實現類,我們要做的是通過接口來調用服務提供者實現的方法。

下面,就是關鍵的服務發現環節,我們寫一個方法,根據型號去調用對應空調的開關方法。

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();
}
}
}
}

測試結果:

d8a8028a-bc7d-11ed-bfe3-dac502259ad0.png

可以看到,測試過程中,通過定義的接口IAircondition發現了兩個實現類,并通過參數,調用了特定實現類的某個方法。整段代碼中沒有出現過具體的服務實現類,操作都是通過接口調用。

5、原理

了解了 spi 的工作流程,我們再來看看它的實現,其實最關鍵的就是上面代碼中出現的ServiceLoader這個類。

上面的示例代碼中,對于ServiceLoader的load()方法的結果,我們用for循環進行了遍歷,這一點我們看一下源碼就能明白,因為ServiceLoader實現了Iterable這一接口,而整個服務發現的核心,就在它的iterator()方法中。

d8ba70fa-bc7d-11ed-bfe3-dac502259ad0.png

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

d8de2a72-bc7d-11ed-bfe3-dac502259ad0.png

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

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

d8ed3f58-bc7d-11ed-bfe3-dac502259ad0.png

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

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

d90653b2-bc7d-11ed-bfe3-dac502259ad0.png

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

d91865b6-bc7d-11ed-bfe3-dac502259ad0.png

在迭代器的迭代過程中,會完成所有實現類的實例化,其實歸根結底,還是基于 java 反射去實現的。

6、應用

要說 spi 的實際應用,大家最常見的應該就是日志框架slf4j了,它利用 spi 實現了插槽式接入其他具體的日志框架。

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

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


org.slf4j
slf4j-api
2.0.3


org.slf4j
slf4j-log4j12
2.0.3

引入項目后,點開它的 jar 包看一下具體結構:

d92accc4-bc7d-11ed-bfe3-dac502259ad0.png

有沒有發現一個彩蛋,先說為什么我們 pom 中引入的明明是slf4j-log4j12,實際上引入的是slf4j-reload4j?翻一下官網的文檔:

d960a876-bc7d-11ed-bfe3-dac502259ad0.png

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

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

7、總結

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






審核編輯:劉清

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • JAVA
    +關注

    關注

    19

    文章

    2958

    瀏覽量

    104544
  • wifi信號
    +關注

    關注

    0

    文章

    17

    瀏覽量

    8309
  • SPI串口
    +關注

    關注

    0

    文章

    4

    瀏覽量

    922

原文標題:美團:SPI 的原理是什么?

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

收藏 人收藏

    評論

    相關推薦

    Sentinel擴展性設計機制分析

    Sentinel 提供多樣的 SPI 接口用于提供擴展的能力。用戶可以在用同一個 sentinel-core 的基礎上自行擴展接口實現,從而可以方便地給 Sentinel 添加自定義的
    的頭像 發表于 10-26 10:03 ?4122次閱讀

    java spring教程

    java spring教程理解Spring 實現原理掌握Spring IOC,AOP掌握Spring的基礎配置和用法熟練使用SSH開發項目
    發表于 09-11 11:09

    聊聊Dubbo - Dubbo可擴展機制實戰

    OSGI容器Dubbo作為一個框架,不希望強依賴其他的IoC容器,比如Spring,Guice。OSGI也是一個很重的實現,不適合Dubbo。最終Dubbo的實現參考了Java原生的SPI
    發表于 06-04 17:33

    聊聊Dubbo - Dubbo可擴展機制源碼解析

    機制Spring容器中去尋找。由于ExtensionFactory本身也是一個擴展點,我們可以實現自己的ExtensionFactory,讓Dubbo的自動裝配支持我們自定義的組件。
    發表于 06-05 18:43

    SPI總線擴展PLC輸出點數的設計與實現_李磊

    SPI總線擴展PLC輸出點數的設計與實現_李磊
    發表于 03-19 11:28 ?2次下載

    基于SPI協議的SD卡讀寫機制實現方法

    基于SPI協議的SD卡讀寫機制實現方法。
    發表于 03-25 11:21 ?27次下載
    基于<b class='flag-5'>SPI</b>協議的SD卡讀寫<b class='flag-5'>機制</b>與<b class='flag-5'>實現</b>方法

    Spring和Springboot的擴展接口總結

    Spring的核心思想就是容器,當容器refresh的時候,外部看上去風平浪靜,其實內部則是一片驚濤駭浪,汪洋一片。Springboot更是封裝了Spring,遵循約定大于配置,加上自動裝配的機制。很多時候我們只要引用了一個依賴
    的頭像 發表于 10-13 10:17 ?904次閱讀

    JDK內置的一種服務SPI機制

    SPI(Service Provider Interface)是JDK內置的一種服務提供發現機制,可以用來啟用框架擴展和替換組件,主要用于框架中開發,例如Dubbo、Spring
    的頭像 發表于 02-15 09:15 ?774次閱讀

    Java中的SPI動態擴展(上)

    基本上,你一說是基于springSPI擴展機制,再把`spring.factories`文件和`EnableAutoConfigurati
    的頭像 發表于 03-24 14:27 ?447次閱讀
    Java中的<b class='flag-5'>SPI</b>動態<b class='flag-5'>擴展</b>(上)

    Java中的SPI動態擴展(下)

    基本上,你一說是基于springSPI擴展機制,再把`spring.factories`文件和`EnableAutoConfigurati
    的頭像 發表于 03-24 14:27 ?537次閱讀
    Java中的<b class='flag-5'>SPI</b>動態<b class='flag-5'>擴展</b>(下)

    Spring中11個最常用的擴展點分享1

    在使用spring的過程中,我們有沒有發現它的擴展能力很強呢?由于這個優勢的存在,使得spring具有很強的包容性,所以很多第三方應用或者框架可以很容易的投入到spring的懷抱中。今
    的頭像 發表于 05-11 10:48 ?530次閱讀

    Spring中11個最常用的擴展點分享2

    在使用spring的過程中,我們有沒有發現它的擴展能力很強呢?由于這個優勢的存在,使得spring具有很強的包容性,所以很多第三方應用或者框架可以很容易的投入到spring的懷抱中。今
    的頭像 發表于 05-11 10:48 ?366次閱讀

    Java、Spring、Dubbo三者SPI機制的原理和區別

    其實我之前寫過一篇類似的文章,但是這篇文章主要是剖析dubbo的SPI機制的源碼,中間只是簡單地介紹了一下Java、SpringSPI機制
    的頭像 發表于 06-05 15:21 ?987次閱讀
    Java、<b class='flag-5'>Spring</b>、Dubbo三者<b class='flag-5'>SPI</b><b class='flag-5'>機制</b>的原理和區別

    什么是SPI機制

    1、前言 在之前的 JVM 分析系列之類加載 提到過 Java SPI 機制,主要是類加載器反雙親委派的實現(第三方包不在指定jdk路徑,一般類加載器無法加載,需要特殊
    的頭像 發表于 10-08 15:03 ?1085次閱讀
    什么是<b class='flag-5'>SPI</b><b class='flag-5'>機制</b>

    通過使用多路復用器實現基于SPI的閃存擴展

    電子發燒友網站提供《通過使用多路復用器實現基于SPI的閃存擴展.pdf》資料免費下載
    發表于 09-21 10:50 ?0次下載
    通過使用多路復用器<b class='flag-5'>實現</b>基于<b class='flag-5'>SPI</b>的閃存<b class='flag-5'>擴展</b>