面試回答
Java 中區(qū)分 API 和 SPI,通俗的講:API 和 SPI 都是相對(duì)的概念,他們的差別只在語義上,API 直接被應(yīng)用開發(fā)人員使用,SPI 被框架擴(kuò)展人員使用。
API Application Programming Interface
大多數(shù)情況下,都是實(shí)現(xiàn)方來制定接口并完成對(duì)接口的不同實(shí)現(xiàn),調(diào)用方僅僅依賴卻無權(quán)選擇不同實(shí)現(xiàn)。
SPI Service Provider Interface
而如果是調(diào)用方來制定接口,實(shí)現(xiàn)方來針對(duì)接口實(shí)現(xiàn)不同的實(shí)現(xiàn)。調(diào)用方來選擇自己需要的實(shí)現(xiàn)方。
知識(shí)擴(kuò)展
如何定義一個(gè) SPI
步驟1、定義一組接口(假設(shè)是 com.chiyi.test.IShout
),并寫出接口的一個(gè)或多個(gè)實(shí)現(xiàn),(假設(shè)是 com.chiyi.test.Dog
、com.chiyi.test.Cat
)。
public interface IShout {
void shout();
}
public class Dog implements IShout{
@Override
public void shout() {
System.out.println("wang wang");
}
}
public class Cat implements IShout{
@Override
public void shout() {
System.out.println("miao miao");
}
}
步驟2、在 src/main/resources/
下建立 /META-INF/services
目錄,新增一個(gè)以接口命名的文件(com.chiyi.test.IShout
文件),內(nèi)容是要應(yīng)用的實(shí)現(xiàn)類(這里是 com.chiyi.test.Dog
和com.chiyi.test.Cat
,每行一個(gè)類)。
com.chiyi.test.Dog
com.chiyi.test.Cat
步驟3、使用 ServiceLoader 來加載配置文件中指定的實(shí)現(xiàn)。
public class Main {
public static void main(String[] args) {
ServiceLoader<IShout> shouts=ServiceLoader.load(IShout.class);
for(IShout s:shouts){
s.shout();
}
}
}
代碼輸出:
wang wang
miao miao
SPI 的實(shí)現(xiàn)原理
看 ServiceLoader 類的簽名類的成員變量:
public final class ServiceLoader<S>
implements Iterable<S>
{
private static final String PREFIX = "META-INF/services/";
// 代表被加載的類或者接口
private final Class<S> service;
// 用于定位,加載和實(shí)例化 providers 的類加載器
private final ClassLoader loader;
// 創(chuàng)建 ServiceLoader 時(shí)采用的訪問控制上下文
private final AccessControlContext acc;
// 緩存 providers,按實(shí)例化的順序排列
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
// 懶查找迭代器
private LazyIterator lookupIterator;
······
}
參考具體源碼,梳理了一下,實(shí)現(xiàn)的流程如下:
- 應(yīng)用程序調(diào)用
ServiceLoader.load
方法,ServiceLoader.load
方法內(nèi)先創(chuàng)建一個(gè)新的ServiceLoader
,并實(shí)例化該類中的成員變量,包括:
-
- loader(ClassLoader 類型,類加載器)
- acc(AccessControlContext 類型,訪問控制器)
- providers(LinkedHashMap 類型,用于緩存加載成功的類)
- lookupIterator(實(shí)現(xiàn)迭代器功能)
- 應(yīng)用程序通過迭代器接口獲取對(duì)象實(shí)例
-
- ServiceLoader 先判斷成員變量 providers 對(duì)象中(LinkedHashMap 類型)是否有緩存實(shí)例對(duì)象,如果有緩存,直接返回。
- 如果沒有緩存,執(zhí)行類的裝載:
-
-
- 讀取 META-INF/services/ 下的配置文件,獲得所有能被實(shí)例化的類的名稱
- 通過反射方法 Class.forName() 加載類對(duì)象,并用 instance() 方法將類實(shí)例化
- 把實(shí)例化的類緩存到 providers 對(duì)象中(LinkedHashMap 類型)
- 然后返回實(shí)例對(duì)象
-
SPI 的應(yīng)用場(chǎng)景
概括地說,適用于:調(diào)用者根據(jù)實(shí)際使用需要,啟用、擴(kuò)展、或者替換框架的實(shí)現(xiàn)策略。
比如常見的例子:
- 數(shù)據(jù)庫驅(qū)動(dòng)加載接口實(shí)現(xiàn)類的加載
- JDBC 加載不同類型數(shù)據(jù)庫的驅(qū)動(dòng)
- 日志門面接口實(shí)現(xiàn)類加載
- SLF4J 加載不同提供商的日志實(shí)現(xiàn)類
Spring
Spring 中大量使用了 SPI,比如:對(duì) servlet3.0 規(guī)范對(duì) ServletContainerInitializer 的實(shí)現(xiàn)、自動(dòng)類型轉(zhuǎn)換 Type Conversion SPI(Converter SPI、Formatter SPI)等
Dubbo文章來源:http://www.zghlxwxcb.cn/news/detail-658915.html
Dubbo 中也大量使用 SPI的方式實(shí)現(xiàn)框架的擴(kuò)展,不過它對(duì) java 提供的原生 SPI 做了封裝,允許用戶擴(kuò)展實(shí)現(xiàn) Filter 接口。文章來源地址http://www.zghlxwxcb.cn/news/detail-658915.html
到了這里,關(guān)于什么是 SPI,和API有什么區(qū)別?的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!