高級Java程序員都必須要清楚的SPI服務擴展思想

一、什麼是SPI

SPI ,全稱爲 Service Provider Interface,是一種服務發現機制。JDK中的SPI是通過在ClassPath路徑下的META-INF/services文件夾查找擴展文件,自動加載文件裏所定義的類。

在小編的理解來,覺得它更是一種思想。即找到服務的接口, 美其名曰: 服務發現機制思想。很多開源框架都有借用這種思想,比如dubbo、jdbc。

二、SPI在JDK中如何使用

SPI在JDK中,我們可以使用 ServiceLoader 類進行使用。

1. 前提準備

public interface SpiService {
    String say();
}

兩個實現類

public class ASpiServiceImpl implements SpiService {
    static {
        System.out.println("static init a");
    }

    {
        System.out.println("init a");
    }

    @Override
    public String say() {
        return "A";
    }
}
public class BSpiServiceImpl implements SpiService {
    static {
        System.out.println("static init b");
    }

    {
        System.out.println("init b");
    }
    @Override
    public String say() {
        return "B";
    }
}

2. 進行配置

在resources中創建META-INF/services目錄

│  └── resources
│      └── META-INF
│          └── services
│              └── com.github.easylog.spi.SpiService

com.github.easylog.spi.SpiService文件內容

com.github.easylog.spi.impl.ASpiServiceImpl
com.github.easylog.spi.impl.BSpiServiceImpl

3. 使用

通過ServiceLoader類我們可以加載到所有配置的實現類,並對實現類進行處理。需要注意一點的是,看4使用注意。

public class SpiTester {
    public static void main(String[] args) {
        ServiceLoader<SpiService> spiServices = ServiceLoader.load(SpiService.class);
        Iterator<SpiService> iterator = spiServices.iterator();
        while (iterator.hasNext()) {
            SpiService next = iterator.next();
            System.out.println(next.say());
        }
    }
}

4. 使用注意

可以看下小編前面聲明的兩個實現類,都定義了靜態代碼塊和非靜態代碼塊。正常情況當這個字節碼被加載,就會執行靜態代碼塊裏面的內容,但是實際運行時候卻沒有執行, 其實是有原因的。

可以看到第二個參數是false。即加載時候不進行初始化。

三、Dubbo中服務發現思想

服務發現這種思想的特點是: 代碼不是硬編碼的方式,而是可配置的。只要將要支持的實現類放到指定配置文件下面,就會自動被加載起來了。然後代碼中只關心使用即可。我們可以利用這種思想來實現, 框架的擴展,比如前面說了。Dubbo會利用SPI的思想進行,加載用戶自定義的過濾器。

這種思想特別適合做服務擴展。現在大多數開源框架中都會使用到這種思想。

1. 定義過濾器

@Activate(group = { Constants.PROVIDER })
public class ProviderHelloFilter implements Filter {
  
    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        System.out.pringln("hello ok!");
        return invoker.invoke(invocation);
    }

}

2. 添加配置文件

META-INF/dubbo/Interal/com.alibaba.dubbo.rpc.Filter

默認支持的過濾器

利用SPI原理,我們自定義一個過濾器

3. 使用

其實API跟JDK中使用ServiceLoader的方式,非常類同。唯一不同的是Dubbo中是使用ExtensionLoader。因爲dubbo中做了一些特殊的增強處理。比如在配置文件中支持自定義一個別名key。如上圖hello就是key。通過getExtension(“hello”)就能獲取指定的實現類。

public class SpiTester {
    public static void main(String[] args) throws Exception{
        ExtensionLoader<Filter> filterExtensionLoader = ExtensionLoader.getExtensionLoader(Filter.class);
        Set<String> supportedExtensions = filterExtensionLoader.getSupportedExtensions();
        System.out.println(supportedExtensions);
        //[accesslog, activelimit, cache...]
        Filter hello = filterExtensionLoader.getExtension("hello");
        //com.github.easylog.spi.ProviderHelloFilter@299a06ac
        System.out.println(hello);
    }
    
}

**那麼這種思想你學會了嗎? **

最後求關注,求訂閱,謝謝你的閱讀!

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