中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

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

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

Java插件擴展機制之SPI的示例分析

發布時間:2021-07-27 13:52:35 來源:億速云 閱讀:199 作者:小新 欄目:開發技術

這篇文章給大家分享的是有關Java插件擴展機制之SPI的示例分析的內容。小編覺得挺實用的,因此分享給大家做個參考,一起跟隨小編過來看看吧。

    什么是SPI

    SPI ,全稱為 Service Provider Interface,是一種服務發現機制。其為框架提供了一個對外可擴展的能力。

    與 接口類-實現類 提供的RPC 方式有什么區別?

    • 傳統的接口類實現形式為如下

    public interface AdOpFromApolloService {}
    public class AdOpFromDbServiceImpl implements AdOpFromDbService {}

    假設我們需要實現RPC,是怎么做的?

    RPC會在對應的接口類AdOpFromApolloService新增一個注解用于標注是RPC類,然后將當前類放在依賴包提供給其他項目來通過接口類進行調用

    簡而言之:RPC調用中只提供接口類,然后讓第三方調用(第三方只調,不寫實現)

    那RPC究竟跟SPI什么關系?

    • SPI:提供接口,第三方引用包之后可重寫或用默認的SPI接口的實現。

    • RPC:提供接口,但是實現是私有的,不對外開放重寫能力。

    SPI的應用場景

    框架實現案例:

    1. Spring擴展插件實現。如JDCB

    2. 中間件擴展插件實現。如Dubbo、Apollo

    3. 開發過程中舉例:實現一個攔截器,并用SPI機制來擴展其攔截方式(比如全量攔截、每分鐘攔截多少、攔截比例多少、攔截的日志是log打印還是落庫、落es)

    怎么實現一個SPI?

    接下來用兩個項目模塊來講解SPI的用法,先看項目結構圖如下

    Java插件擴展機制之SPI的示例分析

    接下來是實現的過程

    第一步:創建spi-demo-contract項目,在resources目錄下新建如下目錄(MATE-INF/services)和文件(com.example.spidemocontract.spi.SpiTestDemoService)

    • 修改com.example.spidemocontract.spi.SpiTestDemoService文件

    • 新增第二步一樣位置的接口類,以及對應的實現類


    第二步:創建spi-demo項目,然后引入spi-demo-contract依賴

    • 在resources目錄下新建如下目錄(MATE-INF/services)和文件(com.example.spidemocontract.spi.SpiTestDemoService),文件名跟spi-demo-contract的完全一致

    • 修改com.example.spidemocontract.spi.SpiTestDemoService(與依賴包的文件名稱完全一致,但內容指向了當前項目自定義的實現類)文件

    • 實現SPI接口,自定義spi-demo項目的實現類(這里可以把優先級調到最高)


    第三步:在spi-demo項目中用ServiceLoader進行加載SPI接口

    補充說明:我們可以重寫聲明類的優先級,來判斷需要用哪個實現類來處理。比如重寫一個優先級=0最高優先級,然后加載的時候默認只取第一個優先級最高的,那我們重寫的自定義實現類就能覆蓋掉默認SPI實現類

    詳細步驟拆分如下

    • 第一步:創建spi-demo-contract項目,在resources目錄下新建如下目錄(MATE-INF/services)和文件(com.example.spidemocontract.spi.SpiTestDemoService)

    -- resources
    ---- META-INF
    -------- services
    ------------ com.example.spidemocontract.spi.SpiTestDemoService
    • 修改com.example.spidemocontract.spi.SpiTestDemoService文件如下

    com.example.spidemocontract.spi.impl.DefaultSpiTestDemoService
    • 新增第二步一樣位置的接口類,以及對應的實現類

    /**
    * 這個接口類完全對應resources/META-INF/services/com.example.spidemocontract.spi.impl.DefaultSpiTestDemoService
    **/
    public interface SpiTestDemoService {
        void printLog();
        int getOrder();
    }
    
    /**
     * 將默認的設置為優先級最低,這是默認的SPI接口的實現類
     */
    public class DefaultSpiTestDemoService implements SpiTestDemoService {
        @Override
        public int getOrder() {
            return Integer.MAX_VALUE;
        }
        @Override
        public void printLog() {
            System.out.println("輸出 DefaultSpiTestDemoService log");
        }
    }
    • 第二步:創建spi-demo項目,然后引入spi-demo-contract依賴

    <dependency>
        <groupId>com.example</groupId>
        <artifactId>spi-demo-contract</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <scope>compile</scope>
    </dependency>
    • 在resources目錄下新建如下目錄(MATE-INF/services)和文件(com.example.spidemocontract.spi.SpiTestDemoService),文件名跟spi-demo-contract的完全一致

    -- resources
    ---- META-INF
    -------- services
    ------------ com.example.spidemocontract.spi.SpiTestDemoService
    • 修改com.example.spidemocontract.spi.SpiTestDemoService(與依賴包的文件名稱完全一致,但內容指向了當前項目自定義的實現類)文件如下

    com.example.spidemo.spi.OtherSpiTestDemoService
    • 實現SPI接口,自定義spi-demo項目的實現類

    /**
     * 其他,把他優先級設置的比較高
     */
    public class OtherSpiTestDemoService implements SpiTestDemoService {
        // 后面我們用SPI類加載器獲取時,會根據order排序,越小優先級越高
        @Override
        public int getOrder() {
            return 0;
        }
        @Override
        public void printLog() {
            System.out.println("輸出 OtherSpiTestDemoService log");
        }
    }
    • 第三步:在spi-demo項目中用ServiceLoader進行加載SPI接口

    public static void main(String[] args) {
        // 加載SPI
        Iterator<SpiTestDemoService> iterator = ServiceLoader.load(SpiTestDemoService.class).iterator();
        // 實現了ordered,會根據ordered返回值排序,優先級越高,越先取出來
        List<SpiTestDemoService> list = Lists.newArrayList(iterator)
                .stream().sorted(Comparator.comparing(SpiTestDemoService::getOrder))
                .collect(Collectors.toList());
        for (SpiTestDemoService item : list) {
            item.printLog();
        }
    }

    中間件是怎么實現SPI的?

    Apollo-Client中的實現

    • Apollo-Client初始化過程中,有一個SPI接口ConfigPropertySourcesProcessorHelper

    // 當前接口會在resource/META-INF/services目錄下對應文件com.ctrip.framework.apollo.spring.spi.ConfigPropertySourcesProcessorHelper
    public interface ConfigPropertySourcesProcessorHelper extends Ordered {
      void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException;
    }
    • 當前SPI中的默認實現為

    public class DefaultConfigPropertySourcesProcessorHelper implements ConfigPropertySourcesProcessorHelper {
        @Override
        public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
            // .....各種注冊bean,初始化的流程
        }
        @Override
        public int getOrder() {
            // 優先級排序置為最低。方便其他自定義spi實現類能夠覆蓋
            return Ordered.LOWEST_PRECEDENCE;
        }
    }
    • 怎么選擇加載出自定義的SPI實現類

    通過ServiceLoader.load(Xxxx.class)加載出所有實例,然后根據order來進行排序優先級,order最小的那個優先級最高,只取第一個數據candidates.get(0)并返回。
    因為自定義SPI實現優先級可以設置得很高,所以就可以達到覆蓋默認實現的目的

    public static <S extends Ordered> S loadPrimary(Class<S> clazz) {
        List<S> candidates = loadAllOrdered(clazz);
        return candidates.get(0);
    }
    public static <S extends Ordered> List<S> loadAllOrdered(Class<S> clazz) {
        Iterator<S> iterator = loadAll(clazz);
    
        if (!iterator.hasNext()) {
          throw new IllegalStateException(String.format(
              "No implementation defined in /META-INF/services/%s, please check whether the file exists and has the right implementation class!",
              clazz.getName()));
        }
        // 獲取迭代中的所有SPI實現實例,然后進行排序,取優先級最高的那個
        List<S> candidates = Lists.newArrayList(iterator);
        Collections.sort(candidates, new Comparator<S>() {
          @Override
          public int compare(S o1, S o2) {
            // the smaller order has higher priority
            return Integer.compare(o1.getOrder(), o2.getOrder());
          }
        });
    
        return candidates;
    }

    JDBC中的實現

    • JDBC SPI中配置文件resources/META-INF/services/java.sql.Driver,并設置參數如下

    com.example.app.driver.MyDriver
    • 繼承SPI接口java.sql.Driver,實現MyDriver

    public class MyDriver extends NonRegisteringDriver implements Driver {
        static {
            try {
                java.sql.DriverManager.registerDriver(new MyDriver());
            } catch (SQLException e) {
                throw new RuntimeException("Can't register driver!", e);
            }
        }
        public MyDriver() throws SQLException {}
        @Override
        public Connection connect(String url, Properties info) throws SQLException {
            System.out.println("MyDriver - 準備創建數據庫連接.url:" + url);
            System.out.println("JDBC配置信息:" + info);
            info.setProperty("user", "root");
            Connection connection = super.connect(url, info);
            System.out.println("MyDriver - 數據庫連接創建完成!" + connection.toString());
            return connection;
        }
    }
    • 寫main方法調用執行剛實現的自定義SPI實現

    String url = "jdbc:mysql://localhost:3306/test?serverTimezone=UTC";
    String user = "root";
    String password = "root";
    Class.forName("com.example.app.driver.MyDriver");
    Connection connection = DriverManager.getConnection(url, user, password);
    • JDBC如何使用SPI簡單分析如下

    獲取所有注冊的驅動(每個驅動的都遍歷一下,其實就是最晚注冊那個就用那個了,如果失敗就往下一個找)

    // 獲取所有注冊的驅動(每個驅動的都遍歷一下,其實就是最晚注冊那個就用那個了,如果失敗就往下一個找)
     for(DriverInfo aDriver : registeredDrivers) {
        // If the caller does not have permission to load the driver then
        // skip it.
        if(isDriverAllowed(aDriver.driver, callerCL)) {
            try {
                println("    trying " + aDriver.driver.getClass().getName());
                Connection con = aDriver.driver.connect(url, info);
                if (con != null) {
                    // Success!
                    println("getConnection returning " + aDriver.driver.getClass().getName());
                    return (con);
                }
            } catch (SQLException ex) {
                if (reason == null) {
                    reason = ex;
                }
            }
    
        } else {
            println("    skipping: " + aDriver.getClass().getName());
        }
    }

    感謝各位的閱讀!關于“Java插件擴展機制之SPI的示例分析”這篇文章就分享到這里了,希望以上內容可以對大家有一定的幫助,讓大家可以學到更多知識,如果覺得文章不錯,可以把它分享出去讓更多的人看到吧!

    向AI問一下細節

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

    AI

    博湖县| 安阳市| 临潭县| 林周县| 宁强县| 璧山县| 九江市| 高阳县| 开平市| 油尖旺区| 布尔津县| 柳州市| 华阴市| 察哈| 平和县| 青神县| 凯里市| 富川| 曲阳县| 隆回县| 宾川县| 额济纳旗| 济宁市| 桃园县| 呼图壁县| 华亭县| 金堂县| 南乐县| 新巴尔虎左旗| 克山县| 高清| 武胜县| 台州市| 乌拉特后旗| 博客| 京山县| 泾川县| 綦江县| 乌兰察布市| 新巴尔虎右旗| 武义县|