Java的SPI

什麼是SPI

SPI全稱Service Provider Interface。沒有翻譯,我的理解是服務提供者的接口。

先看下圖,一般服務提供方提供服務(實現),並暴露接口。這樣調用方只需要調用暴露出的接口即可獲取服務。

也就是說,接口與服務提供方在一起。這是一般的調用模式。
下邊換個思路,接口與調用方在一起。

此時,服務提供方只是實現。不同的服務方可以有不同的實現,通過配置即可切換。有點策略模式的意思。
簡單來說就是,調用方暴露接口,各個服務提供方實現接口提供服務,調用方自己決定使用哪個實現

demo實現

下邊簡單實現一個,新建一個調用項目:包含兩個模塊,hust-contract模塊有一個接口,hust-service發起調用。

這裏爲了簡單起見,引入了spring-boot,代碼如下

接口

package com.hust;

public interface ISpeakInterface {

    String speak(String content);
}

MyController.java

package com.service;

import com.hust.ISpeakInterface;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import java.util.ServiceLoader;


@Controller
@RequestMapping(value = "/kaka")
@RestController
public class MyController {

    @Autowired
    @Qualifier("chinese")
    private ISpeakInterface speakInterface;


    @RequestMapping(value = "/get", method = RequestMethod.GET)
    public String get() {

        ServiceLoader<ISpeakInterface> loaders = ServiceLoader.load(ISpeakInterface.class);
        String s = "";
        for (ISpeakInterface helloService : loaders) {
            s = s + helloService.speak("hello");
        }
        return s;
    }

}

build.gradle

subprojects {
    apply plugin: 'java'
    group 'com.hust'
    version '1.1.1'

    sourceCompatibility = 1.8

    dependencies {
        testCompile group: 'junit', name: 'junit', version: '4.12'

    }

}


project(':hust-contract') {
    dependencies {

    }
}

project(':hust-service') {
    apply plugin: 'application'
    mainClassName = 'com.service.MyMain'

    dependencies {
        compile project(":hust-contract")
        compile "org.springframework:spring-aspects:4.2.4.RELEASE"
        compile "org.springframework.boot:spring-boot-starter:1.5.11.RELEASE"
        compile "org.springframework.boot:spring-boot-starter-web:1.5.11.RELEASE"
        compile "org.springframework.boot:spring-boot-starter-aop:1.5.11.RELEASE"

        compile "com.hust:kaka-provider:1.0.1"
    }
}

再新建一個項目kaka-provider,依賴 hust-contract,並實現了 ISpeakInterface 接口。

ProviderSpeak.java 代碼很簡單

package com.provider;

import com.hust.ISpeakInterface;

public class ProviderSpeak implements ISpeakInterface {
    public String speak(String s) {
        return s + "Provider";
    }
}

代碼很簡單,只是在 resources 目錄下新建了 META_INF/services 目錄,同時新建一個與接口名相同的文件,內容是類名

com.provider.ProviderSpeak

兩個項目關係如下

運行 kakaconsumer的 MyMain,瀏覽器輸入 http://localhost:8080/kaka/get,發現是provider的實現

到此SPI已經實現,從上不難發現,實現SPI需要三步
1.調用方定義接口
2.服務提供者實現接口,並在jar包的META-INF/services目錄下創建一個“接口全名”的文件,內容爲實現類的全名
3.調用方通過ServiceLoader.load方法加載接口的實現

應用場景

目前應用java SPI的地方很多,最多的就是java.sql.Driver,mysql、oracle等各自實現該接口提供服務。

SLF4j

SLF4j,Simple Logging Facade,是存取日誌的標準接口,也就是說它只是一個日誌接口標準,沒有任何實現。這樣,所有的日誌系統實現只要使用了SLF4j,使用時無需關心實現,可以自由替換。

添加依賴

compile "org.apache.logging.log4j:log4j-api:$log4jVersion"

代碼裏使用也很簡單,就一句代碼

Logger logger = LoggerFactory.getLogger(Object.class);

然後就可以用logger輸出日誌了。具體怎麼輸出日誌,由各個日誌實現系統決定。

SLF4J包也很簡單,如下:

暴露SLF4JServiceProvider 接口,同時暴露工廠接口用於獲取對象。

看LoggerFactory.class 源碼:

    public static Logger getLogger(Class<?> clazz) {
        Logger logger = getLogger(clazz.getName());// 1.獲取一個實現
        if (DETECT_LOGGER_NAME_MISMATCH) {
            Class<?> autoComputedCallingClass = Util.getCallingClass();
            if (autoComputedCallingClass != null && nonMatchingClasses(clazz, autoComputedCallingClass)) {
                Util.report(String.format("Detected logger name mismatch. Given name: \"%s\"; computed name: \"%s\".", logger.getName(), autoComputedCallingClass.getName()));
                Util.report("See " + LOGGER_NAME_MISMATCH_URL + " for an explanation");
            }
        }
        return logger;
    }

    private static boolean nonMatchingClasses(Class<?> clazz, Class<?> autoComputedCallingClass) {
        return !autoComputedCallingClass.isAssignableFrom(clazz);
    }

    // ... 省略

    public static ILoggerFactory getILoggerFactory() {
        return getProvider().getLoggerFactory();// 2.獲取Logger的工廠
    }

    static SLF4JServiceProvider getProvider() {
        if (INITIALIZATION_STATE == UNINITIALIZED) {
            synchronized (LoggerFactory.class) {
                if (INITIALIZATION_STATE == UNINITIALIZED) {
                    INITIALIZATION_STATE = ONGOING_INITIALIZATION;
                    performInitialization();// 3. 初始化 provider
                }
            }
        }
        switch (INITIALIZATION_STATE) {
        case SUCCESSFUL_INITIALIZATION:
            return PROVIDER;
        case NOP_FALLBACK_INITIALIZATION:
            return NOP_FALLBACK_FACTORY;
        case FAILED_INITIALIZATION:
            throw new IllegalStateException(UNSUCCESSFUL_INIT_MSG);
        case ONGOING_INITIALIZATION:
            // support re-entrant behavior.
            // See also http://jira.qos.ch/browse/SLF4J-97
            // 避免LoggerFactory還沒賦值時,就請求創建Logger。表明還在創建中
            return SUBST_PROVIDER;
        }
        throw new IllegalStateException("Unreachable code");
    }

這裏先獲取provider,然後用provider初始化

繼續看 performInitialization() 函數:


    private static List<SLF4JServiceProvider> findServiceProviders() {
        ServiceLoader<SLF4JServiceProvider> serviceLoader = ServiceLoader.load(SLF4JServiceProvider.class);
        List<SLF4JServiceProvider> providerList = new ArrayList<SLF4JServiceProvider>();
        for (SLF4JServiceProvider provider : serviceLoader) {
            providerList.add(provider);
        }
        return providerList;
    }
    //... 省略

    static void reset() {
        INITIALIZATION_STATE = UNINITIALIZED;
    }

    private final static void performInitialization() {
        bind();
        if (INITIALIZATION_STATE == SUCCESSFUL_INITIALIZATION) {
            versionSanityCheck();
        }
    }

    private final static void bind() {
        try {
            List<SLF4JServiceProvider> providersList = findServiceProviders();// 4.利用java SPI 獲取所有實現
            reportMultipleBindingAmbiguity(providersList);// 5.如果有多個實現被加載,則報錯
            if (providersList != null && !providersList.isEmpty()) {
            	PROVIDER = providersList.get(0);
            	PROVIDER.initialize();
            	// 6.實現的provider初始化,一般是初始化loggerFactory,markerFactory,mdcAdapter的實現

            	INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;
                reportActualBinding(providersList);
                fixSubstituteLoggers();
                replayEvents();
                // release all resources in SUBST_FACTORY
                SUBST_PROVIDER.getSubstituteLoggerFactory().clear();
            } else {
                INITIALIZATION_STATE = NOP_FALLBACK_INITIALIZATION;
                // 找不到一個Logger的實現,提供默認的 NOPServiceProvider,使用NOPLogger,也就是啥也不做.

                Util.report("No SLF4J providers were found.");
                Util.report("Defaulting to no-operation (NOP) logger implementation");
                Util.report("See " + NO_PROVIDERS_URL + " for further details.");

                Set<URL> staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
                reportIgnoredStaticLoggerBinders(staticLoggerBinderPathSet);
            }
        } catch (Exception e) {
            failedBinding(e);
            throw new IllegalStateException("Unexpected initialization failure", e);
        }
    }

上邊的bind是重點:
ServiceLoader<SLF4JServiceProvider> serviceLoader = ServiceLoader.load(SLF4JServiceProvider.class);
利用java SPI獲取 SLF4JServiceProvider 的一個實現,然後 PROVIDER = providersList.get(0); 即取第一個被加載的實現。如果沒有加載到實現,輸出告警。之後就是各個實現的Logger.info輸出了。

值得一說的是,SLF4J裏面有個默認實現NOPServiceProvider,產生NOPLogger。NOP也就是啥也不提供,裏面是個空實現。

 

Spring

Spring內部有很多實現應用SPI進行解耦

 

Dubbo

Dubbo不光實現了SPI,還在其基礎上做了改進。

 

 

 

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