簡介
什麼是可擴展的應用程序呢?可擴展的意思是不需要修改原始代碼,就可以擴展應用程序的功能。我們將應用程序做成插件或者模塊。
這樣可以在不修改原應用的基礎上,對系統功能進行升級或者定製化。
本文將會向大家介紹如何通過java中的SPI機制實現這種可擴展的應用程序。
SPI簡介
SPI的全稱是Java Service Provider Interface。是java提供的一種服務發現的機制。
通過遵循相應的規則編寫應用程序之後,就可以使用ServiceLoader來加載相應的服務了。
SPI的實現主要分為4個部分:
- Service Provider Interface: SPI是一個interface或者是抽象類,其中定義了我們需要擴展實現的功能。
- Service Providers:這是SPI的具體實現,提供了具體的實現功能
- SPI Configuration File:SPI的配置文件,通過在配置文件我們來配置相關的SPI發現信息。
- ServiceLoader: ServiceLoader是用來加載和發現服務的java類,並提供了很多有用的方法。
SPI的普通java實現
講完SPI的定義,大家可能還是不清楚SPI到底是做什麼的,又該怎麼使用它。
不用急,我們下面通過一個例子來說明。
首先創建一個module:SPI-service,裡面主要定義了一個ModuleService接口:
<code>public interface ModuleService { } /<code>
然後再分別創建兩個module,作為ModuleService的實現:
<code>public class ModuleServiceA implements ModuleService { public ModuleService getModuleService(){ return new ModuleServiceA(); } } /<code>
<code>public class ModuleServiceB implements ModuleService { public ModuleService getModuleService(){ return new ModuleServiceB(); } } /<code>
接著分別在兩個module中創建META-INF/services文件夾,並且在裡面創建兩個以 Service Provider Interface限定名為名字的文件,這裡文件名是:com.flydean.base.service.ModuleService,文件裡面存放的是SPI的具體實現類:
<code>com.flydean.base.servicea.ModuleServiceA com.flydean.base.serviceb.ModuleServiceB /<code>
最後,我們需要創建一個使用SPI的類:
<code>public class ModuleController { public static void main(String[] args) { List moduleServices = ServiceLoader .load(ModuleService.class).stream() .map(ServiceLoader.Provider::get) .collect(toList()); log.info("{}", moduleServices); } } /<code>
為了更好的展示擴展應用的實際使用,我們分別創建4個模塊。在實際應用中,只需要將這些jar包加入應用程序的classpath即可。
運行看下輸出結果:
<code>[com.flydean.base.servicea.ModuleServiceA@16f65612, com.flydean.base.serviceb.ModuleServiceB@311d617d] /<code>
從結果看到,我們獲得了兩個ModuleService。證明系統擴展成功。
SPI在JPMS模塊化系統下的實現
上面我們講的是基本的操作,考慮一下,如果是在JDK9之後,引入了JPMS模塊化系統之後,應該怎麼使用SPI呢?
代碼肯定是一樣,我們需要修改的是SPI配置文件。
如果在JPMS中,我們就不需要使用META-INF/services了,我們只需要創建相應的module-info.java文件即可。
先看下SPI模塊的module-info.java文件:
<code>module com.flydean.service { exports com.flydean.service; } /<code>
這個模塊我們對外暴露了service package,供其他模塊調用。
接下來是SPI的實現模塊:
<code>module com.flydean.servicea { requires com.flydean.service; provides com.flydean.service.ModuleService with com.flydean.servicea.ModuleServiceA; exports com.flydean.servicea; } /<code>
這裡我們使用了provides命令,定義了兩個類的關聯關係。
最後是調用的模塊:
<code>module com.flydean.controller { uses com.flydean.service.ModuleService; requires com.flydean.service; requires lombok; requires slf4j.api; } /<code>
這裡我們使用uses關鍵詞來引用ModuleService。
總結
本文介紹了SPI在模塊化和非模塊化系統中的應用。
本文中的例子:learn-java-base-9-to-20
本文已收錄於 http://www.flydean.com/java-spi-for-extensible-app/
最通俗的解讀,最深刻的乾貨,最簡潔的教程,眾多你不知道的小技巧等你來發現!
歡迎關注我的公眾號:「程序那些事」,懂技術,更懂你!