SPI機制深入分析
概要
SPI全稱是Service Provider Interface. 大多數開發人員可能不熟悉,因為這個機制是針對第三方廠商或者外掛來提供服務的。SPI機制是jdk中java.util.ServiceLoader提供的,後面我們會詳細介紹。
一般我們在做開發的時候,都是抽象各個模組,然後有很多的實現方案,比如日誌模組、xml解析模組、jdbc模組等。在面向物件設計裡,我們一般推薦模組之間最好是基於介面程式設計,而不對具體的實現進行硬編碼。因為一旦程式碼改動涉及到具體的實現類,就違反了可插拔的原則。為了實現模組裝配的時候不在程式裡動態指明,就需要一種服務發現機制。而Java SPI就提供了這樣的一種機制:為某個介面尋找服務實現的機制。這個有點類似與IOC的思想,就是將裝配的控制權移交到程式之外
什麼是SPI
SPI是JDK內建的一種服務提供發現機制。目前市面上有很多框架都是用它來做服務的擴充套件發現。簡單來說,它是一種動態替換髮現的機制。舉個簡單的例子,我們想在執行時動態給它新增實現,你只需要新增一個實現,然後把新的實現描述給JDK知道就行了。
實現SPI需要遵循的標準
我們如何去實現一個標準的SPI發現機制呢?其實很簡單,只需要滿足以下提交就行了
需要在classpath下建立一個目錄,該目錄命名必須是:META-INF/service
在該目錄下建立一個properties檔案,該檔案需要滿足以下幾個條件
2.1 檔名必須是擴充套件的介面的全路徑名稱
2.2 檔案內部描述的是該擴充套件介面的所有實現類
2.3 檔案的編碼格式是UTF-8
通過java.util.ServiceLoader的載入機制來發現
程式碼演示
程式碼結構
程式碼說明
我們定義了一個介面叫IHelloService, 介面中有一個方法
public interface IHelloService {
void sayHello();
}
接著定義了兩個實現類,一個是通過文字的方式來問候,另一個是通過表情的方式來問候,我們來看一下這兩個實現類的程式碼:
public class FaceHelloServiceImpl implements IHelloService{ public void sayHello() { System.out.println("say hello with face:^_^"); } }
public class TextHelloServiceImp implements IHelloService{
public void sayHello() {
System.out.println("say hello with text:你好");
}
}
以上就是核心程式碼了,接著來看一下resources目錄下的檔案,按照上面第二點說的要實現SPI需要滿足的格式:
1. resources/META-INF/service。
2. 檔名要以擴充套件介面的全路徑名:com.gupao.spi.demo.hello.IHelloService.properties
3. 該檔案的內容如下
com.gupao.spi.demo.hello.impl.FaceHelloServiceImpl
com.gupao.spi.demo.hello.impl.TextHelloServiceImp
最後就是呼叫的程式碼了
public class SPIMain
{
public static void main(String[] args) {
ServiceLoader<IHelloService> loader=ServiceLoader.load(IHelloService.class);
for (IHelloService hi:loader){
hi.sayHello();
}
}
}
通過SPIMain方法,就可以分別輸出
say hello with face:^_^
say hello with text:你好
這樣就實現了一個簡單的SPI的實現
SPI的實際應用
其實SPI在很多地方有應用,可能大家都沒有關注,最常用的就是JDBC驅動,我們來看看是怎麼應用的
JDK本身提供了資料訪問的api。在java.sql這個包裡面
我們在連線資料庫的時候,一定需要用到java.sql.Driver這個介面對吧。然後我好奇的去看了下java.sql.Driver的原始碼,發現Driver並沒有實現,而是提供了一套標準的api介面。大家有興趣可以去看看
因為我們在實際應用中用的比較多的是mysql,所以我去mysql的包裡面看到一個如下的目錄結構
看到了嗎? 目錄結構是不是符合SPI的定義?我懷著好奇心,打開了java.sql.Driver這個檔案
這個檔案裡面寫的就是mysql的驅動實現。我恍然大悟,原來通過SPI機制把java.sql.Driver和mysql的驅動做了整合。這樣就達到了各個資料庫廠商自己去實現資料庫連線,jdk本身不關心你怎麼實現。
SPI的缺點
JDK標準的SPI會一次性載入例項化擴充套件點的所有實現,什麼意思呢?就是如果你在META-INF/service下的檔案裡面加了N個實現類,那麼JDK啟動的時候都會一次性全部載入。那麼如果有的擴充套件點實現初始化很耗時或者如果有些實現類並沒有用到,那麼會很浪費資源
如果擴充套件點載入失敗,會導致呼叫方報錯,而且這個錯誤很難定位到是這個原因