成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

從實現(xiàn)到原理,聊聊Java中的SPI動態(tài)擴展

開發(fā) 前端
我們今天暫且不提spring的SPI擴展,先來看看java本身自帶的SPI擴展機制是怎么一回事。

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

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

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

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

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

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

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

1、簡介

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

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

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

簡單形容就是這樣的:

圖片

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

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

Program to an interface, not an implementation.

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

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

圖片

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

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

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

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

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

2、定義接口

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

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

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

圖片

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

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

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

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

public interface IAircondition {
// 獲取型號
String getType();

// 開關(guān)
void turnOnOff();

// 調(diào)節(jié)溫度
void adjustTemperature(int temperature);

// 模式變更
void changeModel(int modelId);
}

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

mvn clean install

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

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

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

<dependency>
<groupId>com.cn.hydra</groupId>
<artifactId>aircondition-standard</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>

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

public class HangingTypeAircondition
implements IAircondition{
public String getType() {
return "HangingType";
}

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

public void adjustTemperature(int i) {
System.out.println("掛式空調(diào)調(diào)節(jié)溫度");
}

public void changeModel(int i) {
System.out.println("掛式空調(diào)更換模式");
}
}

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

com.cn.hydra.HangingTypeAircondition

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

圖片

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

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

public class VerticalTypeAircondition
implements IAircondition{
public String getType() {
return "VerticalType";
}

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

public void adjustTemperature(int i) {
System.out.println("立式空調(diào)調(diào)節(jié)溫度");
}

public void changeModel(int i) {
System.out.println("立式空調(diào)更換模式");
}
}

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

com.cn.hydra.VerticalTypeAircondition

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

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

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

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

<dependencies>
<dependency>
<groupId>com.cn.hydra</groupId>
<artifactId>aircondition-hanging-type</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>

<dependency>
<groupId>com.cn.hydra</groupId>
<artifactId>aircondition-vertical-type</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>

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

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

public class AirconditionApp {
public static void main(String[] args) {
new AirconditionApp().turnOn("VerticalType");
}

public void turnOn(String type){
ServiceLoader<IAircondition> load = ServiceLoader
.load(IAircondition.class);

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

測試結(jié)果:

圖片

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

5、原理

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

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

圖片

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

圖片

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

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

圖片

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

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

圖片

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

圖片

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

6、應(yīng)用

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

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

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

<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.3</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>2.0.3</version>
</dependency>

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

圖片

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

圖片

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

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

7、總結(jié)

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

責(zé)任編輯:姜華 來源: 碼農(nóng)參上
相關(guān)推薦

2023-02-27 22:03:06

數(shù)據(jù)庫內(nèi)存RocketMQ

2025-05-20 05:53:07

DubboSPI機制

2024-12-23 15:05:29

2020-11-20 07:51:02

JavaSPI機制

2022-05-06 08:26:32

JavaSPI機制

2021-07-14 14:05:24

Fragment項目結(jié)構(gòu)

2024-08-05 11:14:45

2025-03-27 02:00:00

SPIJava接口

2022-07-08 08:37:23

Nacos服務(wù)注冊動態(tài)配置

2024-09-04 10:44:19

2024-10-15 08:37:08

2023-06-05 08:07:33

JavaJava SPI

2025-01-15 15:47:36

2025-03-04 09:02:25

JavaSPI機制

2025-01-07 09:07:36

接口屬性路徑

2025-05-08 03:25:00

DubboSPI機制

2020-02-19 19:18:02

緩存查詢速度淘汰算法

2024-09-13 16:47:06

模型量化AI

2024-10-29 08:34:55

SPI機制接口

2024-05-09 09:55:08

點贊
收藏

51CTO技術(shù)棧公眾號

主站蜘蛛池模板: 华丽的挑战在线观看 | 中文字幕亚洲精品 | 1级毛片| 97超碰在线免费 | 国产亚洲一区在线 | 欧美mv日韩mv国产网站91进入 | 美女艹b | 国产精品免费观看视频 | 国产精品久久久爽爽爽麻豆色哟哟 | 成人国产精品视频 | 成人久久18免费网站麻豆 | 在线午夜| 日本三级在线视频 | 在线成人 | 久久久www成人免费精品张筱雨 | 久久国产精品视频 | 男女羞羞视频免费 | 韩国毛片一区二区三区 | 国产精品欧美一区二区 | 日韩欧美在 | 亚洲一区二区av在线 | 国产成人精品一区二 | 国产精品成人在线观看 | 国产精品免费一区二区三区四区 | 91精品久久久 | 久久99精品久久久久 | 国产在线观看av | 亚洲+变态+欧美+另类+精品 | 玖玖玖av| 欧洲一区二区在线 | 久久久久久影院 | 成人一区二区在线 | xx视频在线观看 | 亚洲高清一区二区三区 | 91在线视频免费观看 | 免费激情网站 | 日韩一区精品 | 91国内视频在线 | 自拍偷拍亚洲视频 | 亚洲欧美一区二区三区在线 | 欧美xxxx日本 |