Java SPI機制總結系列之開發入門例項

朱季謙發表於2023-11-11

原創/朱季謙

在該文章正式開始前,先對 Java SPI是什麼做一個簡單的介紹。

SPI,是Service Provider Interface的縮寫,即服務提供者介面,單從字面上看比較抽象,你可以理解成,該機制就像Spring容器一樣,透過IOC將物件的建立交給了Spring容器處理,若需要獲取某個類的物件,就從Spring容器裡取出使用即可。同理,在SPI機制當中,提供了一個類似Spring容器的角色,叫【服務提供者】,在程式碼執行過程中,若要使用到實現了某個介面的服務實現類物件,只需要將對應的介面型別交給服務提供者。服務提供者將會動態載入實現了該介面的所有服務實現類物件。
服務提供者的角色用下圖來表示。
image

舉一個例子來說明。

假如,假如Maven專案裡有這樣一個interface介面,介面全名“com.zhu.service.UserService”——

package com.zhu.service;

public interface UserService {
    void getName();
}

建立一個“com.zhu.service.impl.AUserServiceImpl”實現類——

public class AUserServiceImpl implements UserService {
    @Override
    public void getName() {
        System.out.println("這是A使用者姓名");
    }
}

接著在resource資源裡,建立一個META-INF.services目錄,在該目錄裡,建立一個檔名與介面com.zhu.service.UserService一致的檔案——
image

該com.zhu.service.UserService檔案裡寫下com.zhu.service.impl.UserServiceImpl類名字——
image

這時候,就可以基於Java SPI動態載入到介面的實現類並執行了,我們寫一個簡單的測試類做驗證——

public class Test {
    public static void main(String[] args) {
    ServiceLoader<UserService> serviceLoader = ServiceLoader.load(UserService.class);
    Iterator<UserService> serviceIterator = serviceLoader.iterator();
    while (serviceIterator.hasNext()) {
         UserService service = serviceIterator.next();
         service.getName();
    }
}
    }
}

執行該程式碼,ServiceLoader會載入到META-INF.services目錄下的配置檔案,找到對應介面全名檔案,讀取檔案裡的類名,再透過反射就可以進行實現類的例項化。既然能找到實現類的物件,那麼不就可以基於父類引用指向子類物件,進而呼叫到實現類的getName()方法。該方法裡執行列印語句 System.out.println("列印使用者姓名"),列印結果如下,說明基於介面UserService,在程式動態載入並執行UserService介面實現。
image

Java SPI的機制玩法,就如上文這一整個過程的實現。

該機制存在一個缺陷,假如該介面對應的檔案存在多份實現類,那麼,它都會一起執行了。

我們增加多一個實現類BUserServiceImpl——

public class BUserServiceImpl implements UserService {
    @Override
    public void getName() {
        System.out.println("這是B使用者姓名");
    }
}

然後,在resource資源裡的META-INF.services目錄介面對應com.zhu.service.UserService檔案裡,將BUserServiceImpl實現類的全名增加到檔案裡——
image

其他原有的程式碼無需改動,直接執行Test的main方法,列印結果如下,可以看到,新增的BUserServiceImpl實現類的getName()也被執行了。
image

這就說明,Java SPI機制會將檔案裡配置的所有實現類都動態載入執行,稍微思考了一下,不難發現,若當中某個實現類的getName()出現異常,那麼後面還沒有執行到的其他實現類就會終止了。

因此,Dubbo框架在設計SPI機制時,只是參考了Java SPI的實現,但沒有照搬,相比Java,Dubbo增強了SPI機制,可以針對請求動態得選擇需要的介面實現類來執行,更加靈活方便。我在自己的另一邊原創博文中,詳細介紹過Dubbo SPI的原理,感興趣的小夥伴可以閱讀——Dubbo2.7的Dubbo SPI實現原理細節》

SPI機制的優點很明顯,當我們需要基於已有介面新增一個實現類功能時,只需要新增一個實現類程式碼,無需在原有程式碼邏輯上做改動,就可以實現新增類的功能邏輯了。

這種場景比較適合在報表或者處理Excel文件情況下,需針對一個新報表或者Excel做相應定製化處理,只需要基於SPI已有介面新增一個實現類即可。我會在後續文章中,將過去應用到SPI的實踐經驗做一下總結。

相關文章