亚洲激情专区-91九色丨porny丨老师-久久久久久久女国产乱让韩-国产精品午夜小视频观看

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

java中的spi怎么用

發布時間:2022-03-03 13:53:24 來源:億速云 閱讀:138 作者:小新 欄目:開發技術

這篇文章主要介紹了java中的spi怎么用,具有一定借鑒價值,感興趣的朋友可以參考下,希望大家閱讀完這篇文章之后大有收獲,下面讓小編帶著大家一起了解一下。

    前言

    在開發過程中,經常要用到第三方提供的SDK來完成一些業務擴展功能,比如調用第三方的發短信、圖片驗證碼、人臉識別等等功能,但問題是,第三方SDK只是提供了標準的功能實現,某些場景下,開發者還想基于這些SDK做一些個性化的定制和擴展,那要怎么辦呢?

    于是,一些優秀的SDK就通過SPI的機制,將一些接口擴能開放出來,開發者就可以基于這些SPI接口做自身的業務擴展了;

    總結一下SPI思想:在系統的各個模塊中,往往有不同的實現方案,例如日志模塊的方案、xml解析的方案等,為了在裝載模塊的時候不具體指明實現類,我們需要一種服務發現機制,java spi就提供這樣一種機制。有點類似于IoC的思想,將服務裝配的控制權移到程序之外,在模塊化設計時尤其重要

    Java 的SPI機制在很多框架,中間件等都有著廣泛的使用,如springboot,Dubbo中均有采用,屬于高級Java開發知識點,有必要掌握

    下面用一張簡圖說明下SPI機制的原理

    java中的spi怎么用

    一、JDK中SPI的使用規范

    • 定義通用的服務接口,針對服務接口,提供具體實現類

    • 在jar包的META-INF/services/目錄中,新建一個文件,文件名為 接口的 “全限定名”, 文件內容為該接口的具體實現類的 “全限定名”

    • 將spi所在jar放在主程序的classpath中

    • 服務調用方用java.util.ServiceLoader,用服務接口為參數,去動態加載具體的實現類到JVM中

    案例展示

    案例業務背景:

    • 提供一個統一的支付接口

    • 有兩種支付方式,分別為支付寶支付和微信支付,實際中為不同支付廠商提供的SDK

    • 客戶端為customer工程,即調用支付SDK的使用者

    java中的spi怎么用

    從工程的結構來看,也是遵循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("=======");
            });
        }
    
    }

    運行下這段客戶端的測試程序

    java中的spi怎么用

    我們不妨來看看serviceLoader中的一段關鍵代碼,即加載服務接口時,可以發現,該方法最終要去找接口的實現類所在jar包下的 “META-INF/services” 目錄中的服務實現,如果找到了就能被加載和使用

    java中的spi怎么用

    java中的spi怎么用

    SPI優點

    • 使用Java SPI機制的優勢是實現解耦,使得第三方服務模塊的裝配控制的邏輯與調用者的業務代碼分離

    • 應用程序可根據實際業務情況啟用框架擴展或替換框架組件

    SPI缺點

    • srviceLoader 只能通過遍歷全部獲取,也就是接口的實現類全部加載并實例化一遍

    • 如果并不想用某些實現類,它也被加載并實例化了,這就造成了浪費

    • 獲取某個實現類的方式不夠靈活,只能通過Iterator形式獲取,不能根據某個參數來獲取對應的實現類

    • 多個并發多線程使用ServiceLoader類的實例是不安全的,需要加鎖控制

    SPI機制在實際生產中的一個應用

    在小編的實際項目開發中,有這樣一個需求,標準產品針對單點登錄提供了多種實現,比如 基于cas方案,ldap方案,oauth3.0方案等,針對每種方案,提供了一套具體的實現,即封裝成了各自的jar包

    標準產品在出廠并在客戶端安裝的時候,會有一套默認的實現,即oauth3.0實現,但是客戶方有時候有自己的一套,比如cas服務器,那么客戶希望能夠對接cas單點登錄,這么以來,具體到項目在實際部署的時候,就需要現場做一些特定的參數配置,將標準實現切換為 cas的實現即可,那么問題來了,標準產品是如何根據參數配置做到的呢?

    其實也很簡單,就是使用了 serviceLoader機制,自動發現標準產品中能夠加載到的所有單點登錄實現,如果沒有外部配置參數傳入,則默認使用oauth3.0的實現,否則,將會采用外部參數傳過來的那個實現。

    二、DUbbo 中SPI的使用

    可以說,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 類,用于加載提供者配置文件中所有的實現類,并創建相應的實例

    Dubbo 的 SPI 舉例

    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、定義擴展類配置文件

    java中的spi怎么用

    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());
        }

    java中的spi怎么用

    如果不指定加載哪個,而且接口配置了默認值,這里只需要在getExtension中設置 “true”,就會自動加載默認的那個

    java中的spi怎么用

    在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();  
     
    }

    java中的spi怎么用

    Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
    • Protocol接口,在運行的時候dubbo會判斷一下應該選用這個Protocol接口的哪個實現類來實例化對象

    • 如果你配置了Protocol,則會將你配置的Protocol實現類加載到JVM中來,然后實例化對象時,就用你配置的那個Protocol實現類就可以了

    上面那行代碼就是dubbo里面大量使用的,就是對很多組件,都是保留一個接口和多個實現,然后在系統運行的時候動態的根據配置去找到對應的實現類。如果你沒有配置,那就走默認的實現類,即dubbo

    三、springboot 中SPI思想的使用

    我們知道,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文件的吧

    java中的spi怎么用

    然后來到下面這里,重點關注setInitializers 這個方法,顧名思義,表示在啟動過程中要做的一些初始化設置,那么要設置哪些東西呢?

    java中的spi怎么用

    在這個方法中,有一個方法getSpringFactoriesInstances,緊接著這個方法看進去

    java中的spi怎么用

    在該方法中需要重點關注這句代碼,通過這句代碼,將依賴包下的那些待裝配的文件進行加載,說白了,就是加載classpath下的那些 spring.factory的文件里面的name

    SpringFactoriesLoader.loadFactoryNames(type, classLoader)

    那么問題是具體加載的是什么樣的文件呢?不妨繼續點進去看看,在SpringFactoriesLoader類的開頭,有一個這樣的路徑,想必大家就猜到是什么了吧

    java中的spi怎么用

    也就是說,會去找以這樣的名字結尾的文件,比如我們在依賴的jar包中,看到下面這一幕,在這個spring.factories中,會看到更多我們熟悉的配置

    java中的spi怎么用

    java中的spi怎么用

    這樣問題就很明白了,通過找到spring.factories的文件,然后解析出具體的類的完整的名稱,然后再在:createSpringFactoriesInstances 這個方法中完成對這些 擴展的SPI接口實現類的初始化加載,即完成配的過程

    java中的spi怎么用

    感謝你能夠認真閱讀完這篇文章,希望小編分享的“java中的spi怎么用”這篇文章對大家有幫助,同時也希望大家多多支持億速云,關注億速云行業資訊頻道,更多相關知識等著你來學習!

    向AI問一下細節

    免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

    AI

    类乌齐县| 松桃| 乌兰察布市| 汉源县| 博白县| 西乡县| 老河口市| 营口市| 文安县| 郯城县| 绿春县| 正镶白旗| 义马市| 南郑县| 满洲里市| 秭归县| 益阳市| 宜宾市| 精河县| 新和县| 呼图壁县| 台南市| 宜川县| 布拖县| 清涧县| 津市市| 苏尼特右旗| 饶阳县| 会理县| 阿城市| 延庆县| 衡南县| 罗城| 宁陵县| 霍州市| 达日县| 当阳市| 礼泉县| 乡宁县| 舞阳县| 化州市|