什麼是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,還在其基礎上做了改進。