SPI 是 Java 提供的一種服務加載方式,全名為 Service Provider Interface,可以避免在 Java 代碼中寫死服務的提供者,而是通過 SPI 服務加載機制進行服務的注冊和發現。通過這種方式,可以基于接口編程,實現多個模塊的解耦。
SPI 機制實現解耦
如下的示例展示了通過 ServiceLoader 類加載指定接口的所有服務提供者并進行調用的簡單實現。
1、定義接口 test.DirMonitor,包含一個方法 start();
2、實現接口 test.DirMonitor,定義兩個實現類 test.ObserverMonitor 和 test.LoopMonitor;
3、設置接口的實現類列表。創建目錄 META-INF/services/,新建文件 test.DirMonitor,內容如下:
test.ObserverMonitor
test.LoopMonitor
4、在程序中通過 ServiceLoader 類加載 test.DirMonitor 接口的實現類,并遍歷所有實現類,調用 start() 方法;
從上面的示例可以看出,在代碼中僅僅使用到了接口 test.DirMonitor,并沒有在代碼中使用到具體實現類。通過這種方法,可以實現解耦,接口與實現類可以由不同的開發人員實現,編譯到不同的 jar 包中,甚至實現插件的定義與開發。
spi 包的本地化擴展
java.util.spi 包提供了一些抽象類,可以用于擴展 Java 的本地化服務。本地化 SPI 的使用方法與 ServiceLoader 的 SPI 稍有不同,本地化 SPI 的實現需要打包成 jar 包后,放置于運行的 jre/lib/ext 目錄下方能生效。java.util.spi 包提供的抽象類如下所示:
CalendarDataProvider:為 java.util.Calendar 類的參數提供本地化數據的服務提供者的抽象類;
CalendarNameProvider:為 java.util.Calendar 類的字段提供本地化名稱的服務提供者的抽象類;
CurrencyNameProvider:為 java.util.Currency 提供本地化貨幣符號名稱的服務提供者的抽象類;
LocaleNameProvider:為 java.util.Locale 類提供本地化名稱的服務提供者的抽象類;
LocaleServiceProvider:其他服務提供者抽象類的基類;
ResourceBundleControlProvider:服務接口,用于提供 java.util.ResourceBundle.Control 的實現類;
TimeZoneNameProvider:為 java.util.TimeZone 提供本地化時區的服務提供者的抽象類。
以 CalendarDataProvider 為例,該抽象類用于實現本地化的日歷數據。在下面的例子中,CalendarDataProviderSPI 類設置了每周的第一天為 3,一年中第一周所需的最少天數為 6。
按照 SPI 的要求,在 META-INF/services/ 下建立 java.util.spi.CalendarDataProvider 文件,并寫入 testspi.CalendarDataProviderSPI。打包為 jar 后,放置于 jre/lib/ext 目錄后,執行下面的代碼,則打印的數字分別為 3 和 6,而不再是默認的 1 和 1。
總結
Java 通過 SPI 機制為我們提供了一種服務發現機制。通過在 META-INF/services/ 目錄下創建以接口全限定名為名稱的文件,并在文件中每行寫入一個服務提供者的全限定名,Java 可以發現并創建服務提供者的實例。
通過 ServiceLoader 我們可以獲取指定接口的所有服務提供者,并實現接口調用與服務提供者的解耦。通過實現 spi 包的抽象接口,并放置于 jre/lib/ext 目錄下,可以實現 Java 的本地化。