您好,登錄后才能下訂單哦!
這篇文章主要介紹了java中的spi怎么用,具有一定借鑒價值,感興趣的朋友可以參考下,希望大家閱讀完這篇文章之后大有收獲,下面讓小編帶著大家一起了解一下。
在開發過程中,經常要用到第三方提供的SDK來完成一些業務擴展功能,比如調用第三方的發短信、圖片驗證碼、人臉識別等等功能,但問題是,第三方SDK只是提供了標準的功能實現,某些場景下,開發者還想基于這些SDK做一些個性化的定制和擴展,那要怎么辦呢?
于是,一些優秀的SDK就通過SPI的機制,將一些接口擴能開放出來,開發者就可以基于這些SPI接口做自身的業務擴展了;
總結一下SPI思想:在系統的各個模塊中,往往有不同的實現方案,例如日志模塊的方案、xml解析的方案等,為了在裝載模塊的時候不具體指明實現類,我們需要一種服務發現機制,java spi就提供這樣一種機制。有點類似于IoC的思想,將服務裝配的控制權移到程序之外,在模塊化設計時尤其重要
Java 的SPI機制在很多框架,中間件等都有著廣泛的使用,如springboot,Dubbo中均有采用,屬于高級Java開發知識點,有必要掌握
下面用一張簡圖說明下SPI機制的原理
定義通用的服務接口,針對服務接口,提供具體實現類
在jar包的META-INF/services/目錄中,新建一個文件,文件名為 接口的 “全限定名”, 文件內容為該接口的具體實現類的 “全限定名”
將spi所在jar放在主程序的classpath中
服務調用方用java.util.ServiceLoader,用服務接口為參數,去動態加載具體的實現類到JVM中
案例業務背景:
提供一個統一的支付接口
有兩種支付方式,分別為支付寶支付和微信支付,實際中為不同支付廠商提供的SDK
客戶端為customer工程,即調用支付SDK的使用者
從工程的結構來看,也是遵循SPI的服務規范的,即在resources目錄下,創建一個指定名稱的文件夾,將接口實現的全限定名放進去,那么客戶端只需要依賴特定的SDK,然后通過 serviceLoader的方式即可加載到依賴的SDK的服務
客戶端customer工程導入依賴
<dependencies> <dependency> <artifactId>service-common</artifactId> <groupId>com.congge</groupId> <version>1.0-SNAPSHOT</version> </dependency> <dependency> <artifactId>ali-pay</artifactId> <groupId>com.congge</groupId> <version>1.0-SNAPSHOT</version> </dependency> <dependency> <artifactId>wechat-pay</artifactId> <groupId>com.congge</groupId> <version>1.0-SNAPSHOT</version> </dependency> </dependencies>
public class MainTest { public static void main(String[] args) { ServiceLoader<PayService> loader = ServiceLoader.load(PayService.class); loader.forEach(payService ->{ System.out.println(payService); payService.pay(); System.out.println("======="); }); } }
運行下這段客戶端的測試程序
我們不妨來看看serviceLoader中的一段關鍵代碼,即加載服務接口時,可以發現,該方法最終要去找接口的實現類所在jar包下的 “META-INF/services” 目錄中的服務實現,如果找到了就能被加載和使用
使用Java SPI機制的優勢是實現解耦,使得第三方服務模塊的裝配控制的邏輯與調用者的業務代碼分離
應用程序可根據實際業務情況啟用框架擴展或替換框架組件
srviceLoader 只能通過遍歷全部獲取,也就是接口的實現類全部加載并實例化一遍
如果并不想用某些實現類,它也被加載并實例化了,這就造成了浪費
獲取某個實現類的方式不夠靈活,只能通過Iterator形式獲取,不能根據某個參數來獲取對應的實現類
多個并發多線程使用ServiceLoader類的實例是不安全的,需要加鎖控制
在小編的實際項目開發中,有這樣一個需求,標準產品針對單點登錄提供了多種實現,比如 基于cas方案,ldap方案,oauth3.0方案等,針對每種方案,提供了一套具體的實現,即封裝成了各自的jar包
標準產品在出廠并在客戶端安裝的時候,會有一套默認的實現,即oauth3.0實現,但是客戶方有時候有自己的一套,比如cas服務器,那么客戶希望能夠對接cas單點登錄,這么以來,具體到項目在實際部署的時候,就需要現場做一些特定的參數配置,將標準實現切換為 cas的實現即可,那么問題來了,標準產品是如何根據參數配置做到的呢?
其實也很簡單,就是使用了 serviceLoader機制,自動發現標準產品中能夠加載到的所有單點登錄實現,如果沒有外部配置參數傳入,則默認使用oauth3.0的實現,否則,將會采用外部參數傳過來的那個實現。
可以說,dubbo框架是對spi使用的一個很好的例子,dubbo框架本身就是基于SPI規范做了更進一步的封裝,從上面的優缺點分析中,我媽了解了原生的SPI在客戶端選擇服務的時候需要遍歷所有的接口實現,比較浪費資源,而dubbo在此基礎上有了更好的封裝和實現,下面來了解下dubbo的SPI使用吧
Dubbo 的 SPI 規范是:
接口名:可隨意定義
實現類名:在接口名前添加一個用于表示自身功能的“標識前輟”字符串
提供者配置文件路徑:在依次查找的目錄為
META-INF/dubbo/internal
META-INF/dubbo
META-INF/services
提供者配置文件名稱:接口的全限定性類名,無需擴展名
提供者配置文件內容:文件的內容為 key=value 形式,value 為該接口的實現類的全限類性類名,key 可以隨意,但一般為該實現類的“標識前輟”(首字母小寫)。一個類名占 一行
提供者加載:ExtensionLoader 類相當于 JDK SPI 中的 ServiceLoader 類,用于加載提供者配置文件中所有的實現類,并創建相應的實例
1、創建一個maven工程,并導入核心依賴
<dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo</artifactId> <version>3.0.0</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency>
2、 定義 SPI 接口
比如這里有一個下單的接口,注意接口上需要加上@SPI 注解 ,注解里面的值可以填,也可以不用填,如果填,請注意和配置文件里面的key值名稱保持一致,填寫了的話,加載的時候,會默認找這個key對應的實現
@SPI("alipay") public interface Order { String way(); }
3、定義兩個接口的實現類
public class AlipayOrder implements Order{ public String way() { System.out.println("使用支付寶支付"); return "支付寶支付"; } }
public class WechatOrder implements Order { public String way() { System.out.println("微信支付"); return "微信支付"; } }
4、定義擴展類配置文件
alipay=com.congge.spi.AlipayOrder wechat=com.congge.spi.WechatOrder
5、測試方法
@Test public void test1(){ ExtensionLoader<Order> extensionLoader = ExtensionLoader.getExtensionLoader(Order.class); Order alipay = extensionLoader.getExtension("alipay"); System.out.println(alipay.way()); Order wechat = extensionLoader.getExtension("wechat"); System.out.println(wechat.way()); }
如果不指定加載哪個,而且接口配置了默認值,這里只需要在getExtension中設置 “true”,就會自動加載默認的那個
在Dubbo源碼中,很多地方會存在下面這樣的三種代碼,分別是自適應擴展點、指定名稱的擴展點、激活擴展點,dubbo通過這些擴展的spi接口實現眾多的插拔式功能
ExtensionLoader.getExtensionLoader(xxx.class).getAdaptiveExtension(); ExtensionLoader.getExtensionLoader(xxx.class).getExtension(name); ExtensionLoader.getExtensionLoader(xxx.class).getActivateExtension(url, key);
以dubbo源碼中的Protocol 為例,對應dubbo源碼中的rpc模塊
@SPI("dubbo") public interface Protocol { int getDefaultPort(); @Adaptive <T> Exporter<T> export(Invoker<T> invoker) throws RpcException; @Adaptive <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException; void destroy(); }
Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
Protocol接口,在運行的時候dubbo會判斷一下應該選用這個Protocol接口的哪個實現類來實例化對象
如果你配置了Protocol,則會將你配置的Protocol實現類加載到JVM中來,然后實例化對象時,就用你配置的那個Protocol實現類就可以了
上面那行代碼就是dubbo里面大量使用的,就是對很多組件,都是保留一個接口和多個實現,然后在系統運行的時候動態的根據配置去找到對應的實現類。如果你沒有配置,那就走默認的實現類,即dubbo
我們知道,springboot框架相比spring,從配置文件上簡化了不少,但簡化的只是開發者看到的那些xml配置文件中的東西,其本質仍然未變,就算是少了xml配置文件,依舊在啟動的時候,需要做配置的解析工作,如解析原來的數據庫連接的xml配置文件中的內容加載到spring容器中
而springboot來說,很多看不到的配置文件,都是在容器啟動過程中,自動將配置進行讀取,解析和加載,而在這個過程中,我們不禁好奇,這些配置是存在哪里呢?這里就用到了SPI的思想,也就是涉及到springboot的自動裝配過程
舉例來說,springboot怎么知道啟動時需要加載 DataSource這個數據庫連接的bean對象呢?怎么知道要使用JdbcTemplate 還是Druid的連接呢?
在spingboot工程啟動過程中,有很重要的一個工作,就是完成bean的自動裝配過程,自動裝配裝配的是什么東西呢?簡單來說就是:
掃描classpath(工程目錄下)下所有依賴的jar包裝中指定目錄中以特定的全限定名稱的文件,進行解析并裝配成bean
掃描xml文件,解析xml配置并裝配成bean
解析那些被認為是需要裝配的配置類,如@configuration,@service等
其中第一步中的那些文件是什么呢?其實就是和dubbo或原生的spi規范中的那些 /META-INF 文件,只不過在springboot工程中,命名的格式和規范稍有不同
下面通過源碼來看看springboot啟動過程中是如何加載這些spi文件的吧
然后來到下面這里,重點關注setInitializers 這個方法,顧名思義,表示在啟動過程中要做的一些初始化設置,那么要設置哪些東西呢?
在這個方法中,有一個方法getSpringFactoriesInstances,緊接著這個方法看進去
在該方法中需要重點關注這句代碼,通過這句代碼,將依賴包下的那些待裝配的文件進行加載,說白了,就是加載classpath下的那些 spring.factory的文件里面的name
SpringFactoriesLoader.loadFactoryNames(type, classLoader)
那么問題是具體加載的是什么樣的文件呢?不妨繼續點進去看看,在SpringFactoriesLoader類的開頭,有一個這樣的路徑,想必大家就猜到是什么了吧
也就是說,會去找以這樣的名字結尾的文件,比如我們在依賴的jar包中,看到下面這一幕,在這個spring.factories中,會看到更多我們熟悉的配置
這樣問題就很明白了,通過找到spring.factories的文件,然后解析出具體的類的完整的名稱,然后再在:createSpringFactoriesInstances 這個方法中完成對這些 擴展的SPI接口實現類的初始化加載,即完成配的過程
感謝你能夠認真閱讀完這篇文章,希望小編分享的“java中的spi怎么用”這篇文章對大家有幫助,同時也希望大家多多支持億速云,關注億速云行業資訊頻道,更多相關知識等著你來學習!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。