4、服務發(fā)現(xiàn)
現(xiàn)在兩個服務提供方都實現(xiàn)了接口,下面關鍵的一步就是服務發(fā)現(xiàn),這一步java中的spi發(fā)現(xiàn)機制已經(jīng)幫我們實現(xiàn)好了。
創(chuàng)建一個新項目aircondition-app,引入上面打好的兩個jar包。
<dependencies>
<dependency>
<groupId>com.cn.hydra<span class="hljs-name"groupId>
<artifactId>aircondition-hanging-type<span class="hljs-name"artifactId>
<version>1.0-SNAPSHOT<span class="hljs-name"version>
<span class="hljs-name"dependency>
<dependency>
<groupId>com.cn.hydra<span class="hljs-name"groupId>
<artifactId>aircondition-vertical-type<span class="hljs-name"artifactId>
<version>1.0-SNAPSHOT<span class="hljs-name"version>
<span class="hljs-name"dependency>
<span class="hljs-name"dependencies>
按照上面的說法,雖然每個服務提供者對于接口都有不同的實現(xiàn),但是作為調用者來說,它并不需要關心具體的實現(xiàn)類,我們要做的是通過接口來調用服務提供者實現(xiàn)的方法。
下面,就是關鍵的服務發(fā)現(xiàn)環(huán)節(jié),我們寫一個方法,根據(jù)型號去調用對應空調的開關方法。
public class AirconditionApp {
public static void main(String[] args) {
new AirconditionApp().turnOn("VerticalType");
}
public void turnOn(String type){
ServiceLoader
測試結果:

可以看到,測試過程中,通過定義的接口IAircondition發(fā)現(xiàn)了兩個實現(xiàn)類,并通過參數(shù),調用了特定實現(xiàn)類的某個方法。整段代碼中沒有出現(xiàn)過具體的服務實現(xiàn)類,操作都是通過接口調用。
5、原理
了解了spi的工作流程,我們再來看看它的實現(xiàn),其實最關鍵的就是上面代碼中出現(xiàn)的ServiceLoader這個類。
上面的示例代碼中,對于ServiceLoader的load()方法的結果,我們用for循環(huán)進行了遍歷,這一點我們看一下源碼就能明白,因為ServiceLoader實現(xiàn)了Iterable這一接口,而整個服務發(fā)現(xiàn)的核心,就在它的iterator()方法中。

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

注釋寫的非常明白,providers就是一個緩存,在迭代器中如果先從這里面進行查找,如果里面有就繼續(xù)往下找,沒有了的話就用這個懶加載的lookupIterator查找。
那么就簡單了,接著往下看LazyIterator,看看它里面的hasNext()和next()兩個方法是怎么實現(xiàn)的。

這個acc是一個安全管理器,在前面通過System.getSecurityManager()判斷并賦值,debug看一下這里都是null,所以直接看hasNextService()和nextService()方法就可以了。
在hasNextService()方法中,會取出接口取出實現(xiàn)類的類名放到nextName中:

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

在迭代器的迭代過程中,會完成所有實現(xiàn)類的實例化,其實歸根結底,還是基于java反射去實現(xiàn)的。
6、應用
要說spi的實際應用,大家最常見的應該就是日志框架slf4j了,它利用spi實現(xiàn)了插槽式接入其他具體的日志框架。
說白了,slf4j本身就是個日志門面,并不提供具體的實現(xiàn),需要綁定其他具體實現(xiàn)才能真正的引入日志功能。
例如我們可使用log4j2作為具體的綁定器,只需要在pom中引入slf4j-log4j12,就可以使用具體功能。
org.slf4j
slf4j-api
2.0.3
org.slf4j
slf4j-log4j12
2.0.3
引入項目后,點開它的jar包看一下具體結構:

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

大意就是在2015年和2022年,log4j1.x就已經(jīng)宣布end of life終止了,原因也不難猜,估計是因為頻繁爆出的漏洞。在那之后,slf4j-log4j在構建階段就會自動重定向到slf4j-reload4j了,并且官方也強烈建議使用slf4j-reload4j作為替代。
再回頭看一下jar包的META-INF.services里面,通過spi注入了Reload4jServiceProvider這個實現(xiàn)類,它實現(xiàn)了SLF4JServiceProvider這一接口,在它的初始化方法initialize()中,會完成初始化等工作,后續(xù)可以繼續(xù)獲取到LoggerFactory和Logger等具體日志對象。
7、總結
Java中的SPI提供了一種比較特別的服務發(fā)現(xiàn)和調用機制,通過接口靈活的將服務調用與服務提供者分離,用于提供給第三方實現(xiàn)擴展時還是很方便的。但是也有缺點,比方說一旦加載一個接口,就會把所有實現(xiàn)類都加載進來,可能會加載到不需要的冗余服務。不過站在整體角度上,還是給我們提供了一種非常不錯的框架擴展、集成的思路。
-
JAVA
+關注
關注
20文章
3001瀏覽量
116422 -
SPI
+關注
關注
17文章
1885瀏覽量
101227 -
代碼
+關注
關注
30文章
4968瀏覽量
73960 -
spring
+關注
關注
0文章
341瀏覽量
15935
發(fā)布評論請先 登錄
Java的SPI機制詳解
AG32 下 SPI 的擴展使用
聊聊Dubbo - Dubbo可擴展機制實戰(zhàn)
嵌入式Linux系統(tǒng)中內核抽象的動態(tài)擴展技術
嵌入式Linux系統(tǒng)中內核抽象的動態(tài)擴展技術
嵌入式Linux系統(tǒng)中內核抽象的動態(tài)擴展技術
java的動態(tài)代理機制和作用
java動態(tài)代理分析
英創(chuàng)信息技術JAVA操作英創(chuàng)主板SPI接口簡介
源碼級深度理解Java SPI
基于spring的SPI擴展機制是如何實現(xiàn)的?
Java中的SPI動態(tài)擴展(上)
Java、Spring、Dubbo三者SPI機制的原理和區(qū)別
SPI是什么?Java SPI的使用介紹
Java中的SPI動態(tài)擴展(下)
評論