4、服務(wù)發(fā)現(xiàn)
現(xiàn)在兩個(gè)服務(wù)提供方都實(shí)現(xiàn)了接口,下面關(guān)鍵的一步就是服務(wù)發(fā)現(xiàn),這一步java中的spi發(fā)現(xiàn)機(jī)制已經(jīng)幫我們實(shí)現(xiàn)好了。
創(chuàng)建一個(gè)新項(xiàng)目aircondition-app
,引入上面打好的兩個(gè)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>
按照上面的說(shuō)法,雖然每個(gè)服務(wù)提供者對(duì)于接口都有不同的實(shí)現(xiàn),但是作為調(diào)用者來(lái)說(shuō),它并不需要關(guān)心具體的實(shí)現(xiàn)類(lèi),我們要做的是通過(guò)接口來(lái)調(diào)用服務(wù)提供者實(shí)現(xiàn)的方法。
下面,就是關(guān)鍵的服務(wù)發(fā)現(xiàn)環(huán)節(jié),我們寫(xiě)一個(gè)方法,根據(jù)型號(hào)去調(diào)用對(duì)應(yīng)空調(diào)的開(kāi)關(guān)方法。
public class AirconditionApp {
public static void main(String[] args) {
new AirconditionApp().turnOn("VerticalType");
}
public void turnOn(String type){
ServiceLoader
測(cè)試結(jié)果:
可以看到,測(cè)試過(guò)程中,通過(guò)定義的接口IAircondition
發(fā)現(xiàn)了兩個(gè)實(shí)現(xiàn)類(lèi),并通過(guò)參數(shù),調(diào)用了特定實(shí)現(xiàn)類(lèi)的某個(gè)方法。整段代碼中沒(méi)有出現(xiàn)過(guò)具體的服務(wù)實(shí)現(xiàn)類(lèi),操作都是通過(guò)接口調(diào)用。
5、原理
了解了spi的工作流程,我們?cè)賮?lái)看看它的實(shí)現(xiàn),其實(shí)最關(guān)鍵的就是上面代碼中出現(xiàn)的ServiceLoader
這個(gè)類(lèi)。
上面的示例代碼中,對(duì)于ServiceLoader
的load()
方法的結(jié)果,我們用for
循環(huán)進(jìn)行了遍歷,這一點(diǎn)我們看一下源碼就能明白,因?yàn)?code>ServiceLoader實(shí)現(xiàn)了Iterable
這一接口,而整個(gè)服務(wù)發(fā)現(xiàn)的核心,就在它的iterator()
方法中。
注意這里面有兩個(gè)關(guān)鍵的東西,找一下在源碼中定義的地方:
注釋寫(xiě)的非常明白,providers
就是一個(gè)緩存,在迭代器中如果先從這里面進(jìn)行查找,如果里面有就繼續(xù)往下找,沒(méi)有了的話(huà)就用這個(gè)懶加載的lookupIterator
查找。
那么就簡(jiǎn)單了,接著往下看LazyIterator
,看看它里面的hasNext()
和next()
兩個(gè)方法是怎么實(shí)現(xiàn)的。
這個(gè)acc
是一個(gè)安全管理器,在前面通過(guò)System.getSecurityManager()
判斷并賦值,debug看一下這里都是null
,所以直接看hasNextService()
和nextService()
方法就可以了。
在hasNextService()
方法中,會(huì)取出接口取出實(shí)現(xiàn)類(lèi)的類(lèi)名放到nextName
中:
接下來(lái),在nextService()
方法中,則會(huì)先加載這個(gè)實(shí)現(xiàn)類(lèi),然后實(shí)例化對(duì)象,最終放入緩存中去。
在迭代器的迭代過(guò)程中,會(huì)完成所有實(shí)現(xiàn)類(lèi)的實(shí)例化,其實(shí)歸根結(jié)底,還是基于java反射去實(shí)現(xiàn)的。
6、應(yīng)用
要說(shuō)spi的實(shí)際應(yīng)用,大家最常見(jiàn)的應(yīng)該就是日志框架slf4j
了,它利用spi實(shí)現(xiàn)了插槽式接入其他具體的日志框架。
說(shuō)白了,slf4j
本身就是個(gè)日志門(mén)面,并不提供具體的實(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)開(kāi)它的jar包看一下具體結(jié)構(gòu):
有沒(méi)有發(fā)現(xiàn)一個(gè)彩蛋,先說(shuō)為什么我們pom中引入的明明是slf4j-log4j12
,實(shí)際上引入的是slf4j-reload4j
?翻一下官網(wǎng)的文檔:
大意就是在2015年和2022年,log4j1.x
就已經(jīng)宣布end of life
終止了,原因也不難猜,估計(jì)是因?yàn)轭l繁爆出的漏洞。在那之后,slf4j-log4j
在構(gòu)建階段就會(huì)自動(dòng)重定向到slf4j-reload4j
了,并且官方也強(qiáng)烈建議使用slf4j-reload4j
作為替代。
再回頭看一下jar包的META-INF.services
里面,通過(guò)spi注入了Reload4jServiceProvider
這個(gè)實(shí)現(xiàn)類(lèi),它實(shí)現(xiàn)了SLF4JServiceProvider
這一接口,在它的初始化方法initialize()
中,會(huì)完成初始化等工作,后續(xù)可以繼續(xù)獲取到LoggerFactory
和Logger
等具體日志對(duì)象。
7、總結(jié)
Java中的SPI提供了一種比較特別的服務(wù)發(fā)現(xiàn)和調(diào)用機(jī)制,通過(guò)接口靈活的將服務(wù)調(diào)用與服務(wù)提供者分離,用于提供給第三方實(shí)現(xiàn)擴(kuò)展時(shí)還是很方便的。但是也有缺點(diǎn),比方說(shuō)一旦加載一個(gè)接口,就會(huì)把所有實(shí)現(xiàn)類(lèi)都加載進(jìn)來(lái),可能會(huì)加載到不需要的冗余服務(wù)。不過(guò)站在整體角度上,還是給我們提供了一種非常不錯(cuò)的框架擴(kuò)展、集成的思路。
-
JAVA
+關(guān)注
關(guān)注
20文章
2984瀏覽量
106831 -
SPI
+關(guān)注
關(guān)注
17文章
1775瀏覽量
94649 -
代碼
+關(guān)注
關(guān)注
30文章
4886瀏覽量
70239 -
spring
+關(guān)注
關(guān)注
0文章
340瀏覽量
14878
發(fā)布評(píng)論請(qǐng)先 登錄
Java的SPI機(jī)制詳解

AG32 下 SPI 的擴(kuò)展使用
聊聊Dubbo - Dubbo可擴(kuò)展機(jī)制實(shí)戰(zhàn)
嵌入式Linux系統(tǒng)中內(nèi)核抽象的動(dòng)態(tài)擴(kuò)展技術(shù)
嵌入式Linux系統(tǒng)中內(nèi)核抽象的動(dòng)態(tài)擴(kuò)展技術(shù)
嵌入式Linux系統(tǒng)中內(nèi)核抽象的動(dòng)態(tài)擴(kuò)展技術(shù)
java的動(dòng)態(tài)代理機(jī)制和作用
java動(dòng)態(tài)代理分析
英創(chuàng)信息技術(shù)JAVA操作英創(chuàng)主板SPI接口簡(jiǎn)介

Java實(shí)驗(yàn):類(lèi)和對(duì)象的擴(kuò)展

源碼級(jí)深度理解Java SPI
基于spring的SPI擴(kuò)展機(jī)制是如何實(shí)現(xiàn)的?
Java中的SPI動(dòng)態(tài)擴(kuò)展(上)

Java、Spring、Dubbo三者SPI機(jī)制的原理和區(qū)別

SPI是什么?Java SPI的使用介紹

評(píng)論