SPI(Service Provider Interface)是一种服务发现机制,为某个接口寻找服务实现的机制。这有点类似 IoC 的思想,将装配的控制权移交到了程序之外。SPI 将服务接口和具体的服务实现分离开来,将服务调用方和服务实现者解耦
API:实现方提供了接口和实现,调用方调用实现方的接口进行使用,这就是 API ,即接口和实现都是在实现方
SPI:接口存在于调用方这边时,就是 SPI ,由接口调用方确定接口规则,然后由不同的厂商去根据这个规则对这个接口进行实现,从而提供服务
SLF4J (Simple Logging Facade for Java)是 Java 的一个日志门面(接口),其具体实现有几种,比如:Logback、Log4j、Log4j2 等等,而且还可以切换,在切换日志具体实现的时候我们是不需要更改项目代码的,只需要在 Maven 依赖里面修改一些 pom 依赖就好了
简单的代码示例
1)定义接口
新建logger接口
public interface Logger {
void info(String msg);
void debug(String msg);
}
2)LoggerService
主要是为服务使用者提供特定接口,它不实现接口,但其可以拥有具体的接口实现方法
它能提供功能的核心原因是ServiceLoader 代码中会涉及ServiceLoader的使用,可以先看。这里只需要知道ServiceLoader可以帮服务提供者(LoggerSerive)做以下事情:
1.LoggerService将服务接口注册到/META-INF/services,ServiceLoader通过 URL 工具类从该目录下面找到对应的文件
2.读取这个文件的名称找到对应的待实现的接口
3.通过 InputStream 流将文件里面的具体实现类的全类名读取出来
4.根据获取到的全类名,先判断跟 spi 接口是否为同一类型,如果是的,那么就通过反射的机制构造对应的实例对象,将构造出来的实例对象添加到服务提供者的Providers 的列表中
public class LoggerService {
private static final LoggerService SERVICE = new LoggerService();
private final Logger logger;
//实现Logger接口的方法类
private final List<Logger> loggerList;
private LoggerService() {
ServiceLoader<Logger> loader = ServiceLoader.load(Logger.class);
List<Logger> list = new ArrayList<>();
for (Logger log : loader) {
list.add(log);
}
// LoggerList 是所有 ServiceProvider
loggerList = list;
if (!list.isEmpty()) {
// Logger 只取一个
logger = list.get(0);
} else {
logger = null;
}
}
public static LoggerService getService() {
return SERVICE;
}
public void info(String msg) {
if (logger == null) {
System.out.println("info 中没有发现 Logger 服务提供者");
} else {
logger.info(msg);
}
}
public void debug(String msg) {
if (loggerList.isEmpty()) {
System.out.println("debug 中没有发现 Logger 服务提供者");
}
loggerList.forEach(log -> log.debug(msg));
}
}
此时并没有为 Logger 接口提供任何的实现,所以输出结果中没有按照预期打印相应的结果。
新建一个项目具体实现Logger接口
实现logger接口
public class Logback implements Logger {
@Override
public void info(String s) {
System.out.println("Logback info 打印日志:" + s);
}
@Override
public void debug(String s) {
System.out.println("Logback debug 打印日志:" + s);
}
}
在 src 目录下新建 META-INF/services 文件夹,然后新建文件 xxxx.spi.Logger (SPI 的全类名 即接口的全类名),文件里面的内容是:xxxx.spi.service.Logback (Logback 的全类名,即 SPI 的实现类的包名 + 类名)
public class TestJavaSPI {
public static void main(String[] args) {
//loggerService中可以加载具体的实现类
LoggerService loggerService = LoggerService.getService();
loggerService.info("你好");
loggerService.debug("测试Java SPI 机制");
}
}
接下来,详细看看ServiceLoader
这个类是一个 final 类型的,所以是不可被继承修改,同时它实现了 Iterable 接口。之所以实现了迭代器,是为了方便后续我们能够通过迭代的方式得到对应的服务实现。
public final class ServiceLoader<S> implements Iterable<S>{ xxx...}