什么是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,还在其基础上做了改进。