您好,登錄后才能下訂單哦!
這篇文章主要介紹了JAVA中的SPI是什么意思,具有一定借鑒價(jià)值,感興趣的朋友可以參考下,希望大家閱讀完這篇文章之后大有收獲,下面讓小編帶著大家一起了解一下。
SPI全稱Service Provider Interface,是Java提供的一套用來(lái)被第三方實(shí)現(xiàn)或者擴(kuò)展的接口,其意義在于為某個(gè)接口尋找服務(wù)的實(shí)現(xiàn),主要應(yīng)用在框架中用來(lái)尋找組件,提高擴(kuò)展性。
汽車制造是一個(gè)比較繁瑣的過(guò)程,通常的手段是先規(guī)定汽車各個(gè)零部件的生產(chǎn)規(guī)格,各個(gè)零部件廠商按照這種規(guī)則去生產(chǎn)合格的零部件。汽車生產(chǎn)商挑選合適的零部件去組裝以出產(chǎn)汽車。汽車某個(gè)零部件損壞也不用廢棄掉整個(gè)汽車,只需要更換組件即可。
SPI就是用來(lái)怎么去尋找汽車零部件的一種機(jī)制,生產(chǎn)規(guī)格就是接口的定義,零部件生產(chǎn)商生產(chǎn)零部件就是遵循接口提供具體的實(shí)現(xiàn),SPI挑選合適的組件進(jìn)行組裝后完成特定的功能,當(dāng)某個(gè)組件存在漏洞或問(wèn)題時(shí)可以在不改動(dòng)代碼的前提下替換組件以提高擴(kuò)展性。
SPI旨在為某個(gè)接口尋找服務(wù)的實(shí)現(xiàn),因此在設(shè)計(jì)初期就要規(guī)定好組件的接口,JAVA的SPI機(jī)制使用步驟如下:
定義組件接口(生產(chǎn)規(guī)格)
實(shí)現(xiàn)組件接口(提供組件)
配置組件文件(查找組件)
反射實(shí)例調(diào)用(組裝工作)
組件定義工程中定義組件開發(fā)的規(guī)范,即定義組件需要實(shí)現(xiàn)哪些接口組件A工程和組件B工程提供對(duì)組件的實(shí)現(xiàn),即實(shí)現(xiàn)組件定義的接口組件應(yīng)用工程挑選合適的組件進(jìn)行組件的運(yùn)用
在【commons-api】工程中定義組件的規(guī)范,即定義接口,接口名稱為ComponentService,內(nèi)容如下:
public interface ComponentService { /** * 獲取組件的名稱 * @return 組件名稱 */ String getComponentName(); }
在【component-A】工程中按照組件定義的規(guī)范提供組件,即實(shí)現(xiàn)組件定義接口,類名稱為ComponentA,內(nèi)容如下:
public class ComponentA implements ComponentService { /** * 組件名稱 */ private static final String COMPONENT_NAME = "組件A"; @Override public String getComponentName() { return COMPONENT_NAME; } }
按照J(rèn)AVA的SPI規(guī)則在【component-A】工程的resource/META-INF/services資源目錄下新建文件,文件名稱為組件接口的全限定名,內(nèi)容為組件實(shí)現(xiàn)的全限定名
在【component-B】工程中也提供對(duì)應(yīng)的組件實(shí)現(xiàn),類名稱為ComponentB,內(nèi)容如下:
public class ComponentB implements ComponentService { /** * 組件名稱 */ private static final String COMPONENT_NAME = "組件B"; @Override public String getComponentName() { return COMPONENT_NAME; } }
同樣在【component-B】工程的resource/META-INF/services資源目錄下配置文件
基于maven構(gòu)建的java工程使用pom文件來(lái)編排項(xiàng)目所需要的依賴組件,現(xiàn)在需要用到組件,并且我覺得A組件比B組件更適合我,如是在【component-application】工程的pom中我編排了組件A,內(nèi)容如下:
<dependencies> <dependency> <groupId>com.xxxx</groupId> <artifactId>component-A</artifactId> <version>1.0-SNAPSHOT</version> </dependency> </dependencies>
在【component-application】工程中新建應(yīng)用程序啟動(dòng)類來(lái)運(yùn)用組件,類名稱為Main,內(nèi)容如下:
public class Main { public static void main(String[] args) { // 加載組件 ServiceLoader<ComponentService> components = ServiceLoader.load(ComponentService.class); for (ComponentService component : components) { // 運(yùn)用組件:打印組件名稱 System.out.println(component.getComponentName()); } } }
啟動(dòng)【component-application】工程的Main類的main方法,結(jié)果如下:
假如某一天我發(fā)現(xiàn)組件A存在很大漏洞,需要更換組件將組件A替換成組件B,只需要在【component-application】工程的pom中去掉組件A的依賴,導(dǎo)入組件B的依賴即可,pom內(nèi)容如下:
<dependencies> <dependency> <groupId>com.xxxx</groupId> <artifactId>component-B</artifactId> <version>1.0-SNAPSHOT</version> </dependency> </dependencies>
無(wú)需修改【component-application】工程的具體使用細(xì)節(jié),就可以達(dá)到替換組件的目的,運(yùn)行如下:
JAVA提供的SPI機(jī)制主要依靠的是java.util.ServiceLoader類,從SPI案例中入手,進(jìn)入ServiceLoader.load方法一探究竟。
load方法最終會(huì)創(chuàng)建一個(gè)LazyIterator 的實(shí)例,看到Iterator大概就知道和迭代器有關(guān),繼續(xù)了解一下LazyIterator 是什么
猜想得不錯(cuò),LazyIterator 和迭代器有關(guān),它是ServiceLoader的一個(gè)內(nèi)部類,實(shí)現(xiàn)了Iterator接口,那只需要重點(diǎn)關(guān)注Iterator接口定義的方法
public boolean hasNext()
public S next()
Iterator接口定義的hasNext方法用于判斷迭代的是否是否還有下一個(gè)元素,LazyIterator 的hasNext方法最終是調(diào)用的hasNextService方法,重點(diǎn)研究這個(gè)。
通過(guò)類加載器獲取到指定目錄下的資源文件配置的組件實(shí)現(xiàn)的全限定名,存放在configs的一個(gè)容器變量中,有了組件實(shí)現(xiàn)的全限定名,后面多半就是反射生成對(duì)象返回了,繼續(xù)看一下LazyIterator 的next方法。LazyIterator的next方法主要邏輯是在nextService方法中,仔細(xì)分析一下
和剛才的猜想一致,拿到了組件實(shí)現(xiàn)的全限定名通過(guò)Class.forName來(lái)生成組件對(duì)象,所以程序就通過(guò)for循環(huán)得到了對(duì)象可以進(jìn)行調(diào)用。
java.util.ServiceLoader類是這樣來(lái)描述自己的
A simple service-provider loading facility.
一個(gè)簡(jiǎn)單的服務(wù)提供者加載工具
The only requirement enforced by this facility is thatprovider classes must have a zero-argument constructor so that they can beinstantiated during loading
強(qiáng)制執(zhí)行的唯一要求是提供者類必須有一個(gè)無(wú)參的構(gòu)造函數(shù),以便它們可以在加載過(guò)程中實(shí)例化,從Class.forName生成實(shí)例對(duì)象就可以看出使用的是無(wú)參構(gòu)造
SPI的這種組件替換思想很容易讓人想到我們熟知的JDBC規(guī)范。JDBC是JAVA規(guī)定的應(yīng)用程序連接數(shù)據(jù)庫(kù)的標(biāo)準(zhǔn),定義了連接數(shù)據(jù)庫(kù)的幾個(gè)接口,比如Connection、Statement、ResultSet。各個(gè)數(shù)據(jù)庫(kù)廠商提供自己的JDBC實(shí)現(xiàn),這就是我們所說(shuō)的數(shù)據(jù)庫(kù)驅(qū)動(dòng)。開發(fā)人員只需要關(guān)心如何使用JDBC的各個(gè)接口,不需要學(xué)習(xí)不同廠商的實(shí)現(xiàn),這就是面向接口編程。
JDBC編程分為四個(gè)步驟,SPI在驅(qū)動(dòng)管理器DriverManager中得到了應(yīng)用
Mysql驅(qū)動(dòng)和SqlServer驅(qū)動(dòng)都有SPI的配置
在驅(qū)動(dòng)管理器的loadInitialDrivers方法中就會(huì)通過(guò)SPI機(jī)制獲取可用的驅(qū)動(dòng),loadInitialDrivers方法會(huì)在靜態(tài)代碼塊中被調(diào)用。這里并沒有通過(guò)全限定名反射實(shí)例化,真正的驅(qū)動(dòng)注冊(cè)是數(shù)據(jù)庫(kù)廠商提供的驅(qū)動(dòng)類中通過(guò)靜態(tài)代碼塊將驅(qū)動(dòng)注冊(cè)到驅(qū)動(dòng)管理器中的registeredDrivers集合變量中的,以MySQL驅(qū)動(dòng)為例:
在驅(qū)動(dòng)管理器的getConnection方法中會(huì)遍歷SPI查找到的可用驅(qū)動(dòng),通過(guò)驅(qū)動(dòng)獲取鏈接,直至鏈接獲取成功才返回。
感謝你能夠認(rèn)真閱讀完這篇文章,希望小編分享的“JAVA中的SPI是什么意思”這篇文章對(duì)大家有幫助,同時(shí)也希望大家多多支持億速云,關(guān)注億速云行業(yè)資訊頻道,更多相關(guān)知識(shí)等著你來(lái)學(xué)習(xí)!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。