JDK與Dubbo中的SPI詳細(xì)介紹
1、SPI簡(jiǎn)介
SPI 全稱為 (Service Provider Interface) ,是JDK內(nèi)置的一種服務(wù)提供發(fā)現(xiàn)機(jī)制。 目前有不少框架用它來(lái)做服務(wù)的擴(kuò)展發(fā)現(xiàn),簡(jiǎn)單來(lái)說(shuō),它就是一種動(dòng)態(tài)替換發(fā)現(xiàn)的機(jī)制。使用SPI機(jī)制的優(yōu)勢(shì)是實(shí)現(xiàn)解耦,使得第三方服務(wù)模塊的裝配控制邏輯與調(diào)用者的業(yè)務(wù)代碼分離。
2、JDK中的SPI

Java中如果想要使用SPI功能,先提供標(biāo)準(zhǔn)服務(wù)接口,然后再提供相關(guān)接口實(shí)現(xiàn)和調(diào)用者。這樣就可以通過(guò)SPI機(jī)制中約定好的信息進(jìn)行查詢相應(yīng)的接口實(shí)現(xiàn)。
SPI遵循如下約定:
- 當(dāng)服務(wù)提供者提供了接口的一種具體實(shí)現(xiàn)后,在META-INF/services目錄下創(chuàng)建一個(gè)以“接口全限定名”為命名的文件,內(nèi)容為實(shí)現(xiàn)類的全限定名;
- 接口實(shí)現(xiàn)類所在的jar包放在主程序的classpath中;
- 主程序通過(guò)java.util.ServiceLoader動(dòng)態(tài)裝載實(shí)現(xiàn)模塊,它通過(guò)掃描META-INF/services目錄下的配置文件找到實(shí)現(xiàn)類的全限定名,把類加載到JVM;
- SPI的實(shí)現(xiàn)類必須攜帶一個(gè)無(wú)參構(gòu)造方法;
入門案例
模塊結(jié)構(gòu)如下

(1)api模塊
此模塊創(chuàng)建了一個(gè)接口,供后續(xù)模塊使用
package com.lagou.service;
public interface HelloService {
String sayHello();
}(2)impl模塊
此模塊中提供了api模塊接口的實(shí)現(xiàn)類,并且在resource目錄下,創(chuàng)建了配置文件。
實(shí)現(xiàn)類
package com.lagou.service.impl;
import com.lagou.service.HelloService;
public class HumanHelloService implements HelloService {
@Override
public String sayHello() {
return "hello world";
}
}配置文件(名稱為接口全限定名, 內(nèi)容為接口實(shí)現(xiàn)類全限定類名)
com.lagou.service.impl.HumanHelloService
(3)main模塊
提供了測(cè)試jdk-spi的的類
package com.lagou.test;
import com.lagou.service.HelloService;
import java.util.ServiceLoader;
public class JavaSpiMain {
public static void main(String[] args) {
final ServiceLoader<HelloService> helloServices = ServiceLoader.load(HelloService.class);
for (HelloService helloService : helloServices) {
System.out.println(helloServices.getClass().getName() + ";" + helloService.sayHello());
}
}
}3、Dubbo中的SPI
dubbo中大量的使用了SPI來(lái)作為擴(kuò)展點(diǎn),通過(guò)實(shí)現(xiàn)同一接口的前提下,可以進(jìn)行定制自己的實(shí)現(xiàn)類。比如比較常見(jiàn)的協(xié)議,負(fù)載均衡,都可以通過(guò)SPI的方式進(jìn)行定制化,自己擴(kuò)展。Dubbo中已經(jīng)存在的所有已經(jīng)實(shí)現(xiàn)好的擴(kuò)展點(diǎn)。

下圖中則是Dubbo中默認(rèn)提供的負(fù)載均衡策略。

4、Dubbo中擴(kuò)展點(diǎn)使用方式
我們使用三個(gè)項(xiàng)目來(lái)演示Dubbo中擴(kuò)展點(diǎn)的使用方式,一個(gè)主項(xiàng)目main,一個(gè)服務(wù)接口項(xiàng)目api,一個(gè)服務(wù)實(shí)現(xiàn)項(xiàng)目impl。
(1)api模塊
往pom.xml中導(dǎo)入dubbo依賴
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
<version>2.7.5</version>
</dependency>創(chuàng)建接口,并在接口上添加上@SPI注解
package com.lagou.service;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.extension.Adaptive;
import org.apache.dubbo.common.extension.SPI;
@SPI
public interface HelloService {
String sayHello();
}(2)impl模塊
導(dǎo)入 api項(xiàng)目 的依賴
建立實(shí)現(xiàn)類
package com.lagou.service;
public class HumanHelloService implements HelloService{
@Override
public String sayHello() {
return "hello 你好";
}
}SPI進(jìn)行聲明操作,在resources 目錄下創(chuàng)建目錄META-INF/dubbo 目錄,在目錄下創(chuàng)建名稱為接口的全限定類名的文件,文件內(nèi)部配置實(shí)現(xiàn)類名稱和對(duì)應(yīng)的全限定名:
human=com.lagou.service.HumanHelloService
(3)main模塊
導(dǎo)入坐標(biāo)、接口項(xiàng)目和實(shí)現(xiàn)類項(xiàng)目
創(chuàng)建DubboSpiMain
和原先調(diào)用的方式不太相同, dubbo 有對(duì)其進(jìn)行自我重新實(shí)現(xiàn) 需要借助ExtensionLoader,創(chuàng)建新的運(yùn)行項(xiàng)目。這里demo中的示例和java中的功能相同,查詢出所有的已知實(shí)現(xiàn),并且調(diào)用
package com.lagou;
import com.lagou.service.HelloService;
import org.apache.dubbo.common.extension.ExtensionLoader;
import java.util.Set;
public class DubboSpiMain {
public static void main(String[] args) {
// 獲取擴(kuò)展加載器
ExtensionLoader<HelloService> extensionLoader = ExtensionLoader.getExtensionLoader(HelloService.class);
// 遍歷所有支持的擴(kuò)展點(diǎn) META-INF.dubbo
Set<String> extensions = extensionLoader.getSupportedExtensions();
for (String extension : extensions) {
String result = extensionLoader.getExtension(extension).sayHello();
System.out.println(result);
}
}
}(4)dubbo自己做SPI的目的
1. JDK 標(biāo)準(zhǔn)的 SPI 會(huì)一次性實(shí)例化擴(kuò)展點(diǎn)所有實(shí)現(xiàn),如果有擴(kuò)展實(shí)現(xiàn)初始化很耗時(shí),但如果沒(méi)用上也加載,會(huì)很浪費(fèi)資源
2. 如果有擴(kuò)展點(diǎn)加載失敗,則所有擴(kuò)展點(diǎn)無(wú)法使用
3. 提供了對(duì)擴(kuò)展點(diǎn)包裝的功能(Adaptive),并且還支持通過(guò)set的方式對(duì)其他的擴(kuò)展點(diǎn)進(jìn)行注入
5、DubboSPI中的Adaptive功能
Dubbo中的Adaptive功能,主要解決的問(wèn)題是如何動(dòng)態(tài)的選擇具體的擴(kuò)展點(diǎn)。通過(guò)getAdaptiveExtension 統(tǒng)一對(duì)指定接口對(duì)應(yīng)的所有擴(kuò)展點(diǎn)進(jìn)行封裝,通過(guò)URL的方式對(duì)擴(kuò)展點(diǎn)來(lái)進(jìn)行動(dòng)態(tài)選擇。 (dubbo中所有的注冊(cè)信息都是通過(guò)URL的形式進(jìn)行處理的。)這里同樣采用相同的方式進(jìn)行實(shí)現(xiàn)。
(1)創(chuàng)建接口
api中的HelloService 擴(kuò)展如下方法, 與原先類似,在sayHello中增加Adaptive 注解,并且在參數(shù)中提供URL參數(shù).注意這里的URL參數(shù)的類為org.apache.dubbo.common.URL,其中@SP可以指定一個(gè)字符串參數(shù),用于指明該SPI的默認(rèn)實(shí)現(xiàn)。
package com.lagou.service;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.extension.Adaptive;
import org.apache.dubbo.common.extension.SPI;
@SPI("human") // 這個(gè)human對(duì)應(yīng)配置文件中的human
public interface HelloService {
String sayHello();
@Adaptive
String sayHello(URL url);
}(2)創(chuàng)建實(shí)現(xiàn)類
與上面Service實(shí)現(xiàn)類代碼相似,只需增加URL形參即可
package com.lagou.service;
import org.apache.dubbo.common.URL;
public class HumanHelloService implements HelloService{
@Override
public String sayHello() {
return "hello 你好";
}
@Override
public String sayHello(URL url) {
return "hello url";
}
}(3)編寫DubboAdaptiveMain
最后在獲取的時(shí)候方式有所改變,需要傳入U(xiǎn)RL參數(shù),并且在參數(shù)中指定具體的實(shí)現(xiàn)類參數(shù)
package com.lagou;
import com.lagou.service.HelloService;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.extension.ExtensionLoader;
public class DubboAdaptiveMain {
public static void main(String[] args) {
URL url = URL.valueOf("test://localhost?hello.service=human");
HelloService adaptiveExtension = ExtensionLoader.getExtensionLoader(HelloService.class).getAdaptiveExtension();
String hello = adaptiveExtension.sayHello(url);
System.out.println(hello);
}
}注意:
- 因?yàn)樵谶@里只是臨時(shí)測(cè)試,所以為了保證URL規(guī)范,前面的信息均為測(cè)試值即可,關(guān)鍵的點(diǎn)在于hello.service 參數(shù),這個(gè)參數(shù)的值指定的就是具體的實(shí)現(xiàn)方式。關(guān)于為什么叫hello.service 是因?yàn)檫@個(gè)接口的名稱,其中后面的大寫部分被dubbo自動(dòng)轉(zhuǎn)碼為. 分割。
- 通過(guò)getAdaptiveExtension 來(lái)提供一個(gè)統(tǒng)一的類來(lái)對(duì)所有的擴(kuò)展點(diǎn)提供支持(底層對(duì)所有的擴(kuò)展點(diǎn)進(jìn)行封裝)。
- 調(diào)用時(shí)通過(guò)參數(shù)中增加URL 對(duì)象來(lái)實(shí)現(xiàn)動(dòng)態(tài)的擴(kuò)展點(diǎn)使用。
- 如果URL沒(méi)有提供該參數(shù),則該方法會(huì)使用默認(rèn)在SPI 注解中聲明的實(shí)現(xiàn)。
到此這篇關(guān)于JDK與Dubbo中的SPI詳細(xì)介紹的文章就介紹到這了,更多相關(guān)JDK與Dubbo的SPI內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
解決maven父子工程install的時(shí)候排除某些子模塊,讓子模塊不install問(wèn)題
在Maven父子工程中,如果希望某個(gè)子模塊不被安裝到本地倉(cāng)庫(kù),可以在該子模塊的`pom.xml`文件中添加以下配置: ```xml ... org.apache.maven.plugins maven-install-plugin 2.5.2 true2024-12-12
Spring與Struts整合之讓Spring管理控制器操作示例
這篇文章主要介紹了Spring與Struts整合之讓Spring管理控制器操作,結(jié)合實(shí)例形式詳細(xì)分析了Spring管理控制器相關(guān)配置、接口實(shí)現(xiàn)與使用技巧,需要的朋友可以參考下2020-01-01
nacos配置實(shí)例權(quán)重不生效問(wèn)題
這篇文章主要介紹了nacos配置實(shí)例權(quán)重不生效問(wèn)題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-07-07
SpringBoot接口或方法進(jìn)行失敗重試的實(shí)現(xiàn)方式
為了防止網(wǎng)絡(luò)抖動(dòng),影響我們核心接口或方法的成功率,通常我們會(huì)對(duì)核心方法進(jìn)行失敗重試,如果我們自己通過(guò)for循環(huán)實(shí)現(xiàn),會(huì)使代碼顯得比較臃腫,所以本文給大家介紹了SpringBoot接口或方法進(jìn)行失敗重試的實(shí)現(xiàn)方式,需要的朋友可以參考下2024-07-07
SpringBoot和前端Vue的跨域問(wèn)題及解決方案
所謂跨域就是從 A 向 B 發(fā)請(qǐng)求,如若他們的地址協(xié)議、域名、端口都不相同,直接訪問(wèn)就會(huì)造成跨域問(wèn)題,跨域是非常常見(jiàn)的現(xiàn)象,這篇文章主要介紹了解決SpringBoot和前端Vue的跨域問(wèn)題,需要的朋友可以參考下2023-11-11

