作者 | 京東云開發(fā)者-京東零售 曹志飛
原文鏈接:https://my.oschina.NET/u/4090830/blog/10106105
SPI 是什么
SPI 全稱 Service Provider Interface,是 JAVA 提供的一套用來被第三方實(shí)現(xiàn)或者擴(kuò)展的 API,它可以用來啟用框架擴(kuò)展和替換組件。
整體機(jī)制如下圖
Java SPI 實(shí)際上是 “基于接口的編程+策略模式+配置文件” 組合實(shí)現(xiàn)的動(dòng)態(tài)加載機(jī)制。
使用場景
適用于:調(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 中大量使用了 SPI, 比如:對 servlet3.0 規(guī)范對 ServletContAInerInitializer 的實(shí)現(xiàn)、自動(dòng)類型轉(zhuǎn)換 Type Conversion SPI (Converter SPI、Formatter SPI) 等
- Dubbo 中也大量使用 SPI 的方式實(shí)現(xiàn)框架的擴(kuò)展,不過它對 Java 提供的原生 SPI 做了封裝,允許用戶擴(kuò)展實(shí)現(xiàn) Filter 接口
要使用 Java SPI,需要遵循如下約定:
- 當(dāng)服務(wù)提供者提供了接口的一種具體實(shí)現(xiàn)后,在 jar 包的 META-INF/services 目錄下創(chuàng)建一個(gè)以 “接口全限定名” 為命名的文件,內(nèi)容為實(shí)現(xiàn)類的全限定名;
- 接口實(shí)現(xiàn)類所在的 jar 包放在主程序的 classpath 中;
- 主程序通過 java.util.ServiceLoder 動(dòng)態(tài)裝載實(shí)現(xiàn)模塊,它通過掃描 META-INF/services 目錄下的配置文件找到實(shí)現(xiàn)類的全限定名,把類加載到 JVM;
- SPI 的實(shí)現(xiàn)類必須攜帶一個(gè)不帶參數(shù)的構(gòu)造方法;
- 優(yōu)點(diǎn):使用 Java SPI 機(jī)制的優(yōu)勢是實(shí)現(xiàn)解耦,使得第三方服務(wù)模塊的裝配控制的邏輯與調(diào)用者的業(yè)務(wù)代碼分離,而不是耦合在一起。應(yīng)用程序可以根據(jù)實(shí)際業(yè)務(wù)情況啟用框架擴(kuò)展或替換框架組件。
- 缺點(diǎn):
- 雖然 ServiceLoader 也算是使用的延遲加載,但是基本只能通過遍歷全部獲取,也就是接口的實(shí)現(xiàn)類全部加載并實(shí)例化一遍。如果你并不想用某些實(shí)現(xiàn)類,它也被加載并實(shí)例化了,這就造成了浪費(fèi)。獲取某個(gè)實(shí)現(xiàn)類的方式不夠靈活,只能通過 Iterator 形式獲取,不能根據(jù)某個(gè)參數(shù)來獲取對應(yīng)的實(shí)現(xiàn)類。
- 多個(gè)并發(fā)多線程使用 ServiceLoader 類的實(shí)例是不安全的。
END