Java Service Provider Interface(SPI)

組件中最常用的解耦及擴展機制的基石頭。

什麼是Service?

Service是一組知名的接口和類(類通常都是抽象類),Service Provider是Service的一種特定實現。Service Provider中的類實現了Service中定義的接口或者是Service中定義的類的子類。Java平臺可以以擴展的方式安裝Service Provider,也就是說可以將Service Provider的jar放置在任何常用的擴展目錄之中。Service Provider可以通過放置在應用的class path或以其他平臺支持的方式提供服務。

ServiceLoader

ServiceLoader是Java提供的一個用於加載Service Provider的簡單工具。出於加載的目的,Service必須由單一類型表示,即單一接口或抽象類也可以使用具體類但是不建議。特定的Service Provider包含一個或多個具體類,這些類用特定於Provider的數據和代碼擴展此Service。Provider類通常不是整個Provider本身,而是包含足夠信息以決定提供者是否能夠滿足特定請求以及可以按需創建實際Provider的代理。Provider類和特定的Service高度相關,不可能由單個類或接口統一定義,因此Java規範也並未就此定義。ServiceLoader對於Provider的唯一要求就是Provider有默認的構造函數已確保在加載Provider過程中創建Provider實例。

Service Provider配置

META-INF/services 中的配置文件用於唯一標識一個ServiceProvider。配置文件的名字是定義Service的接口或類的全限定名。文件內容爲具體的ServicePorvider實現類全限定名,每行一個定義。文件使用#號註釋,而且必須使用UTF-8編碼。

如果一個具體的Service Provider類包含在多個配置文件中,或者一個配置文件中包含多個Service Provider,重複的將會被忽略。配置文件中配置的Service Provider無需和配置文件在同一個jar或其他分發單元之中。Service Provider必須可以使用加載配置文件的class loader訪問,其中class loader不一定是實際加載配置文件的class loader。

ServiceLoader 的加載機制

兩個詞描述ServiceLoader的加載機制就是:緩存和懶加載。

ServiceLoader緩存已經加載的Provider,並可以通過reload() 方法清空緩存。ServiceLoader懶加載Provider,只有當需要Provider時比如方法調用才真正的加載實例化Provider。

下面是ServiceLoader首次加載的流程示意圖:

安全性

service loader一直在調用者的安全上下文中執行。可信系統代碼通常應從特權安全上下文中調用此類中的方法以及它們返回的迭代器的方法。

線程安全

ServiceLoader是非線程安全的。

無特殊說明傳遞給ServiceLoader的任何參數爲null,ServiceLoader都會拋出NullPointerException 異常。

3 Service Provider樣例

開發一個Service Provider需要上面的四個步驟:1、首先定義Service或實現其他Service;2、實現Service定義的功能;3、配置以讓ServiceLoader可以加載Service實現;4、加載Service Provider使用。下面以一個獲取主機地址列表爲例一步一步講解如何實現:

3.1 定義服務

定義一個只有獲取地址列表的服務IHostProvider

public interface IHostProvider {
    /**
     * 獲取主機地址列表
     * @return
     */
    List<String> hosts();
}

3.2 實現IHostProvider 服務

爲了區分不同的Provider,分別實現獲取本地地址列表和遠程地址列表兩個Provider

Local Host Provider:

package io.github.ctlove0523.spi.local;

import io.github.ctlove0523.spi.api.IHostProvider;

import java.util.Collections;
import java.util.List;

public class LocalHostProvider implements IHostProvider {
    public LocalHostProvider() {
        System.out.println("local host provider loaded");
    }

    @Override
    public List<String> hosts() {
        return Collections.singletonList("localhost");
    }
}

Remote Host Provider:

package iot.github.ctlove0523.spi.remote;

import io.github.ctlove0523.spi.api.IHostProvider;

import java.util.Collections;
import java.util.List;

public class RemoteHostProvider implements IHostProvider {
    public RemoteHostProvider() {
        System.out.println("remote host provider loaded");
    }

    @Override
    public List<String> hosts() {
        return Collections.singletonList("10.23.45.67");
    }

}

3.3 增加配置文件

Local Host Provider 增加如下配置文件:

resources/META-INF/services/io.github.ctlove0523.spi.api.IHostProvider

文件的內容爲:

io.github.ctlove0523.spi.local.LocalHostProvider

Remote Host Provider 增加如下配置文件:

resources/META-INF/services/io.github.ctlove0523.spi.api.IHostProvider

文件的內容爲:

iot.github.ctlove0523.spi.remote.RemoteHostProvider

3.4 加載Provider

引入Local Host ProviderRemote Host Provider 以及IHostProvider 三個Jar文件。下面第一段程序說明了ServiceLoader的懶加載機制:

ServiceLoader<IHostProvider> hostProviders = ServiceLoader.load(IHostProvider.class);
        System.out.println(hostProviders.iterator().hasNext() ? "load success" : "load failed");

輸出:

load success

並沒有輸出Provider默認構造函數的信息,說明ServiceLoader還沒有真正的實例化Provider,這證明了上面提到的ServiceLoader懶加載機制。

第二段程序獲取所有的IHostProvider的實現並調用方法:

ServiceLoader<IHostProvider> hostProviders = ServiceLoader.load(IHostProvider.class);
hostProviders.iterator().forEachRemaining(new Consumer<IHostProvider>() {
    @Override
    public void accept(IHostProvider iHostProvider) {
        System.out.println(iHostProvider.hosts());
    }
});

輸出:

local host provider loaded
[localhost]
remote host provider loaded
[10.23.45.67]

從輸出來看ServiceLoader真正的實例化了每一個Provider。

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