Java SPI 與 Dubbo SPI 有什麼區別?

作者:廢物大師兄<br> 來源:www.cnblogs.com/cjsblog/p/14346766.html

SPI(Service Provider Interface)是JDK內置的一種服務提供發現機制。本質是將接口實現類的全限定名配置在文件中,並由服務加載器讀取配置文件,加載實現類。這樣可以在運行時,動態爲接口替換實現類。

在Java中SPI是被用來設計給服務提供商做插件使用的。基於策略模式來實現動態加載的機制。我們在程序只定義一個接口,具體的實現交個不同的服務提供者;在程序啓動的時候,讀取配置文件,由配置確定要調用哪一個實現。有很多組件的實現,如日誌、數據庫訪問等都是採用這樣的方式,最常用的就是 JDBC 驅動。

1、Java SPI

核心類:java.util.ServiceLoader

服務是一組衆所周知的接口和(通常是抽象的)類。服務提供者是服務的特定實現。提供者中的類通常實現接口,並子類化服務本身中定義的類。服務提供者可以以擴展的形式安裝在Java平臺的實現中,即放置在任何常見擴展目錄中的jar文件。提供程序也可以通過將它們添加到應用程序的類路徑或其他特定於平臺的方法來提供。

通過在資源目錄META-INF/services中放置一個提供程序配置文件來識別服務提供程序。文件名是服務類型的完全限定二進制名稱。該文件包含具體提供程序類的完全限定二進制名的列表,每行一個。每個名稱周圍的空格和製表符以及空白行將被忽略。註釋字符是'#';在每一行中,第一個註釋字符之後的所有字符都將被忽略。文件必須用UTF-8編碼。

按照上面的方法,我們來寫個例子試一下

首先,定義一個接口Car

package org.example;

public interface Car {
    void run();
}

兩個實現類

ToyotaCar.java

package org.example;

public class ToyotaCar implements Car {
    @Override
    public void run() {
        System.out.println("Toyota");
    }
}

HondaCar.java

package org.example;

public class HondaCar implements Car {
    @Override
    public void run() {
        System.out.println("Honda");
    }
}

在META-INF/services下創建一個名爲org.example.Car的文本文件

org.example.ToyotaCar
org.example.HondaCar

最後,寫個測試類運行看一下效果

package org.example;

import java.util.ServiceLoader;

public class App
{
    public static void main( String[] args )
    {
        ServiceLoader<Car> serviceLoader = ServiceLoader.load(Car.class);
        serviceLoader.forEach(x->x.run());
    }
}

跟一下ServiceLoader的代碼,看看是怎麼找到服務實現的

用當前線程的類加載器加載

接口和類加載器都有了,萬事俱備只欠東風

Java SPI 不足之處:

  • 不能按需加載。Java SPI在加載擴展點的時候,會一次性加載所有可用的擴展點,很多是不需要的,會浪費系統資源
  • 獲取某個實現類的方式不夠靈活,只能通過 Iterator 形式獲取,不能根據某個參數來獲取對應的實現類
  • 不支持AOP與IOC
  • 如果擴展點加載失敗,會導致調用方報錯,導致追蹤問題很困難

2、Dubbo SPI

Dubbo重新實現了一套功能更強的SPI機制, 支持了AOP與依賴注入,並且利用緩存提高加載實現類的性能,同時支持實現類的靈活獲取。

<dependency>
      <groupId>org.apache.dubbo</groupId>
      <artifactId>dubbo</artifactId>
      <version>2.7.8</version>
</dependency>

核心類:org.apache.dubbo.common.extension.ExtensionLoader

先來了解一下@SPI註解,@SPI是用來標記接口是一個可擴展的接口

改造一下前面的例子,在Car接口上加上@SPI註解

package org.example;

import org.apache.dubbo.common.extension.SPI;

@SPI
public interface Car {
    void run();
}

兩個實現類不變

在META-INF/dubbo目錄下創建名爲org.example.Car的文本文件,內容如下(鍵值對形式):

toyota=org.example.ToyotaCar
honda=org.example.HondaCar

編寫測試類:

package org.example;

import org.apache.dubbo.common.extension.ExtensionLoader;

import java.util.ServiceLoader;

public class App
{
    public static void main( String[] args )
    {
        //  Java SPI
        ServiceLoader<Car> serviceLoader = ServiceLoader.load(Car.class);
        serviceLoader.forEach(x->x.run());

        //  Dubbo SPI
        ExtensionLoader<Car> extensionLoader = ExtensionLoader.getExtensionLoader(Car.class);
        Car car = extensionLoader.getExtension("honda");
        car.run();
    }
}

下面跟一下代碼

如果緩存Map中有,直接返回,沒有則加載完以後放進去

加載策略到底是怎樣的呢?

到這裏就有點明白了,又看到了熟悉的ServiceLoad.load(),這不是剛纔講的Java SPI嘛

回到之前策略那個地方,將策略按順序排列,依次遍歷所有的策略來加載。就是在那三個目錄下查找指定的文件,並讀取其中的內容

跟之前的ServiceLoader如出一轍

遇到@Adaptive標註的就緩存起來

最後,大家關注公衆號Java技術棧,在後臺回覆:面試,可以獲取我整理的 Java、Dubbo 系列面試題和答案,非常齊全。

近期熱文推薦:

1.1,000+ 道 Java面試題及答案整理(2021最新版)

2.終於靠開源項目弄到 IntelliJ IDEA 激活碼了,真香!

3.阿里 Mock 工具正式開源,幹掉市面上所有 Mock 工具!

4.Spring Cloud 2020.0.0 正式發佈,全新顛覆性版本!

5.《Java開發手冊(嵩山版)》最新發布,速速下載!

覺得不錯,別忘了隨手點贊+轉發哦!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章