Java中的SPI機(jī)制使用解析
SPI機(jī)制簡(jiǎn)述
SPI(Service Provider Interface的縮寫) 意思是:“服務(wù)提供者的接口”,專門提供給服務(wù)提供者或者擴(kuò)展框架功能的開發(fā)者去使用的接口。SPI 將服務(wù)接口和服務(wù)實(shí)現(xiàn)分離開來,將服務(wù)調(diào)用方和服務(wù)實(shí)現(xiàn)方進(jìn)行解耦,能夠提升程序的擴(kuò)展性和可維護(hù)性,當(dāng)修改或替換服務(wù)實(shí)現(xiàn)并不需要修改調(diào)用方。 SPI 是JDK內(nèi)置的一種動(dòng)態(tài)加載擴(kuò)展點(diǎn)的實(shí)現(xiàn)。
場(chǎng)景
很多框架都使用了JAVA 的 SPI 機(jī)制,如:數(shù)據(jù)庫加載驅(qū)動(dòng)、日志接口以及DUBBO的擴(kuò)展實(shí)現(xiàn)等。
SPI與API的區(qū)別
(1)API(Application Programming Interface的縮寫),一般都是由實(shí)現(xiàn)方制定接口并完成對(duì)接口的實(shí)現(xiàn),調(diào)用方僅依賴接口調(diào)用,并無法選擇不同實(shí)現(xiàn)。
(2)SPI(Service Provider Interface的縮寫),一般都是由調(diào)用方來制定接口規(guī)范,將接口規(guī)范提供給外部廠商來實(shí)現(xiàn),調(diào)用方在調(diào)用時(shí)可以選擇不同的實(shí)現(xiàn)。

SPI的實(shí)現(xiàn)
(1)定義一個(gè)公共接口(一般定義在一個(gè)公共的模塊中,實(shí)現(xiàn)方和調(diào)用方都會(huì)引用該模塊);
(2)實(shí)現(xiàn)方需要實(shí)現(xiàn)剛剛定義的公共接口;
(3)實(shí)現(xiàn)方在“META-INF/services”目錄下新建一個(gè)名稱為接口的全限定名的文本文件,文件內(nèi)容為接口實(shí)現(xiàn)類的全限定名(一般會(huì)在resources目錄下創(chuàng)建如上文件);
(4)調(diào)用方通過ServiceLoader#load方法加載接口的實(shí)現(xiàn)類實(shí)例。
SPI的示例代碼
在“模塊1”中定義一個(gè)公共接口“Hello”,分別在“模塊2”和“模塊2”兩個(gè)模塊中實(shí)現(xiàn)剛剛定義的接口作為兩個(gè)實(shí)現(xiàn)方,在“模塊4”中通過ServiceLoader進(jìn)行測(cè)試并作為調(diào)用方。
// 1. 公共接口(模塊1):
package com.hadoopx.common.util;
public interface Hello {
void say();
}
// 2. 具體實(shí)現(xiàn)1(模塊2):
package com.hadoopx.m1.test;
import com.hadoopx.common.util.Hello;
public class Chinese implements Hello {
@Override
public void say() {
System.out.println("你好!");
}
}
// 3. 創(chuàng)建文件: resources/META-INF/services/com.hadoopx.common.util.Hello,內(nèi)容如下:
com.hadoopx.m1.test.Chinese
// 4. 具體實(shí)現(xiàn)2(模塊3):
package com.hadoopx.m2.test;
import com.hadoopx.common.util.Hello;
public class English implements Hello {
@Override
public void say() {
System.out.println("HELLO WORLD!");
}
}
// 5. 創(chuàng)建文件: resources/META-INF/services/com.hadoopx.common.util.Hello,內(nèi)容如下:
com.hadoopx.m2.test.English
// 6. 調(diào)用方(模塊4):
package com.hadoopx.test;
import com.hadoopx.common.util.Hello;
import java.util.Iterator;
import java.util.ServiceLoader;
public class Test {
public static void main(String[] args) {
ServiceLoader<Hello> loader = ServiceLoader.load(Hello.class);
Iterator<Hello> iterator = loader.iterator();
while (iterator.hasNext()) {
Hello hello = iterator.next();
hello.say();
}
}
}JAVA與SPRING關(guān)于SPI的區(qū)別
SPRING SPI對(duì)JAVA SPI進(jìn)行了封裝和增強(qiáng),調(diào)用方可以使用SpringFactoriesLoader.loadFactories方法得到配置文件中寫的實(shí)現(xiàn)類的集合,其要求如下:
(1)配置文件必須在resources/META-INF/目錄下,文件名必須為spring.factories;
(2)文件內(nèi)容為鍵值對(duì)形式,一個(gè)鍵可以有多個(gè)值(需要用逗號(hào)分割),鍵值都要求是類的全限定名,鍵名為接口的全限定名。
在SPRING BOOT的自動(dòng)裝配過程中,最終會(huì)加載META-INF/spring.factories文件,加載的過程是由SpringFactoriesLoader加載的,它會(huì)從CLASSPATH下的每個(gè)JAR包中搜尋所有META-INF/spring.factories配置文件并解析,然后將其中定義的BEAN注入到SPRING容器。
// 1. 公共接口(模塊1):
package com.hadoopx.common.util;
public interface Hello {
void say();
}
// 2. 具體實(shí)現(xiàn)1(模塊2):
package com.hadoopx.m1.test;
import com.hadoopx.common.util.Hello;
public class Chinese implements Hello {
@Override
public void say() {
System.out.println("你好!");
}
}
// 3. 創(chuàng)建/修改文件: resources/META-INF/spring.factories,內(nèi)容如下:
com.hadoopx.common.util.Hello = \
com.hadoopx.m1.test.Chinese
// 4. 具體實(shí)現(xiàn)2(模塊3):
package com.hadoopx.m2.test;
import com.hadoopx.common.util.Hello;
public class English implements Hello {
@Override
public void say() {
System.out.println("HELLO WORLD!");
}
}
// 5. 創(chuàng)建/修改文件: resources/META-INF/spring.factories,內(nèi)容如下:
com.hadoopx.common.util.Hello = \
com.hadoopx.m2.test.English
// 6. 調(diào)用方(模塊4):
package com.hadoopx.test;
import com.hadoopx.common.util.Hello;
import java.util.Iterator;
import java.util.ServiceLoader;
public class Test {
public static void main(String[] args) {
List<Hello> hellos = SpringFactoriesLoader.loadFactories(Hello.class, null);
for(Hello hello: hellos) {
hello.say();
};
}
}SPI的缺點(diǎn)
(1)ServiceLoader會(huì)遍歷加載所有的實(shí)現(xiàn)類,效率相對(duì)較低;
(2)當(dāng)由多個(gè)ServiceLoader同時(shí)加載時(shí),會(huì)存在并發(fā)的問題。
到此這篇關(guān)于Java中的SPI機(jī)制使用解析的文章就介紹到這了,更多相關(guān)SPI機(jī)制使用詳解內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
基于@JsonSerialize和@JsonInclude注解使用方法
這篇文章主要介紹了@JsonSerialize和@JsonInclude注解使用方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-10-10
springboot捕獲全局異常實(shí)現(xiàn)過程
本文主要介紹了Java中的異常和錯(cuò)誤,包括Exception和Error的區(qū)別、如何捕捉全局異常、自定義異常的實(shí)現(xiàn)等,通過實(shí)例代碼和步驟,展示了如何在Spring?Boot項(xiàng)目中實(shí)現(xiàn)全局異常處理,并自定義異常類來增強(qiáng)程序的健壯性2026-03-03
使用MyBatis 動(dòng)態(tài)update數(shù)據(jù)
使用mybatis寫sql,需要?jiǎng)討B(tài)更新對(duì)象數(shù)據(jù),每次需要更新的字段不同,為了防止null空異常,就需要用動(dòng)態(tài)sql了,下面給大家分享一段代碼關(guān)于mybatis動(dòng)態(tài)update,需要的朋友參考下2016-11-11
java判斷一個(gè)文件是否為二進(jìn)制文件的方法
這篇文章主要介紹了java判斷一個(gè)文件是否為二進(jìn)制文件的方法,涉及java針對(duì)文件的讀取及編碼判斷技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-07-07
java org.springframework.boot 對(duì)redis操作方法
在Spring Boot項(xiàng)目中操作Redis,你可以使用Spring Data Redis,Spring Data Redis是Spring提供的一個(gè)用于簡(jiǎn)化Redis數(shù)據(jù)訪問的模塊,它提供了一個(gè)易于使用的編程模型來與Redis交互,本文給大家介紹java org.springframework.boot 對(duì)redis操作方法,感興趣的朋友一起看看吧2025-04-04
Java微信授權(quán)登陸的實(shí)現(xiàn)示例
微信授權(quán)登錄,官方文檔寫的比較簡(jiǎn)潔。所以對(duì)于會(huì)的人一目了然,而對(duì)于新手剛?cè)腴T的人來說是舉步維艱。本文詳細(xì)的介紹了Java微信授權(quán)登陸的實(shí)現(xiàn)示例,感興趣的朋友可以了解一下2021-06-06

