SPI基本概念
SPI(Service Provider Interface)是一种服务发现机制,为某个接口寻找服务实现的机制。这有点类似 IoC 的思想,将装配的控制权移交到了程序之外。SPI 将服务接口和具体的服务实现分离开来,将服务调用方和服务实现者解耦
SPI 和 API的区别
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 是所有 ServiceProviderloggerList = 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接口
Logback
实现logger接口
public class Logback implements Logger {@Overridepublic void info(String s) {System.out.println("Logback info 打印日志:" + s);}@Overridepublic 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(源码部分阅读待补充)
接下来,详细看看ServiceLoader
这个类是一个 final 类型的,所以是不可被继承修改,同时它实现了 Iterable 接口。之所以实现了迭代器,是为了方便后续我们能够通过迭代的方式得到对应的服务实现。
public final class ServiceLoader<S> implements Iterable<S>{ xxx...}