概述
目录
- 什么是 SPI
- SPI 原理
- Spring.factories
- 实现原理
- 应用
什么是 SPI
即 Service Provider Interface
。是 JDK 内置的一种服务提供发现机制,为某个接口寻找服务的实现,在模块化设计中这个机制很重要。
下面以一个简单的 demo 来示意 SPI 的用法:
- 首先定义一个接口
public interface SPIService {
void execute();
}
- 我们在两个模块中实现这个接口:
public class SpiImpl1 implements SPIService{
public void execute() {
System.out.println("SpiImpl1.execute()");
}
}
public class SpiImpl2 implements SPIService{
public void execute() {
System.out.println("SpiImpl2.execute()");
}
}
- 最后需要在
MTEA-INF/services
路径下创建一个配置文件:
文件名是接口名
文件内容是实现类的全限定名
com.test.spi.SpiImpl1
com.test.spi.SpiImpl2
- 执行:
public class SPITest {
public static void main(String[] args) {
// 用法 1
Iterator<SPIService> providers = Service.providers(SPIService.class);
while (providers.hasNext()) {
SPIService ser = providers.next();
ser.execute();
}
// 用法 2
ServiceLoader<SPIService> load = ServiceLoader.load(SPIService.class);
Iterator<SPIService> iterator = load.iterator();
while (iterator.hasNext()) {
SPIService ser = iterator.next();
ser.execute();
}
}
}
- 输出
SpiImpl1.execute()
SpiImpl2.execute()
SpiImpl1.execute()
SpiImpl2.execute()
可以看到,我们首先需要获取到接口的所有实现类,即服务提供者 providers,然后依次调用其实现的接口方法,即可实现所有服务提供者的调用。
SPI 原理
- 构建新的
ServiceLoader
对象
ServiceLoader<SPIService> load = ServiceLoader.load(SPIService.class);
- 传入当前线程的类加载器,构造一个新的
ServiceLoader
对象。在ServiceLoader
的构造方法中,创建了一个LazyIterator
:是一个迭代器的实现类。
public static <S> ServiceLoader<S> load(Class<S> service) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
public static <S> ServiceLoader<S> load(Class<S> service,
ClassLoader loader){
return new ServiceLoader<>(service, loader);
}
// 创建 LazyIterator
lookupIterator = new LazyIterator(service, loader);
注意 ServiceLoader
类定义 :
public final class ServiceLoader<S>
implements Iterable<S>
表示是一个支持迭代的类,其 iterator
迭代器即为上述创建的 LazyIterator
- 获取其
Iterator
迭代器
Iterator<SPIService> iterator = load.iterator();
hasNext
,是否还有元素
while (iterator.hasNext())
- 即
LazyIterator
中hasNext
。 扫描所有 jar 包中的MTEA-INF/services
路径下目标接口的配置文件,并 解析 其中所有的实现类名字。
Class<S> service;
ClassLoader loader;
Enumeration<URL> configs = null;
Iterator<String> pending = null;
String nextName = null;
private boolean hasNextService() {
if (configs == null) {
// 首次调用,进行资源加载
try {
String fullName = PREFIX + service.getName();
if (loader == null)
configs = ClassLoader.getSystemResources(fullName);
else
configs = loader.getResources(fullName);
} catch (IOException x) {
fail(service, "Error locating configuration files", x);
}
}
// 解析 configs
while ((pending == null) || !pending.hasNext()) {
if (!configs.hasMoreElements()) {
return false;
}
pending = parse(service, configs.nextElement());
}
nextName = pending.next();
return true;
}
- 获取下一个元素
SPIService ser = iterator.next();
- 将解析出的类的全限定名,通过反射加载,实例化,加载到缓存中、并返回。
// Cached providers, in instantiation order
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
Class<?> c = null;
// 反射加载
c = Class.forName(cn, false, loader);
// 实例化
S p = service.cast(c.newInstance());
// 加入缓存
providers.put(cn, p);
return p;
Spring.factories
Spring factories
是 Spring Boot 一种解耦的扩展机制,仿照了 Java 中 SPI 的扩展机制,可以实现自动加载可扫描包之外的 Bean。
与 SPI 非常类似,只不过 Spring factories
是在 META-INF/spring.factories
文件中配置接口的实现类名称,然后在程序中读取这些配置文件并实例化。
这一机制也正是 Spring Boot 实现自动配置的重要原理,是 Spring-Boot-Starter
实现的基础。
周知 Spring Boot 是约定大于配置的思想,简单可理解为将所有的配置项都给出一个默认值,我们可以直接使用此默认值进行工作,即进行了约定。
在项目内部,我们可以自定义一些配置文件,使用 @ComponentScan
+ @Configuration
注解即可实现自动扫描并加载,那么项目之外的配置呢?比如当我们引用了一个插件提供的 Spring-Boot-Starter
时,其为我们自动提供了一些默认的配置值,那么如何将依赖包中的配置 bean 加载到我们的容器中呢?
此时就可以使用 Factories 机制:插件在其提供的 jar 包的 spring.factories
文件中指定需要自动注册到 Spring 容器的 bean 和一些配置信息。使用该插件的开发人员只需要在服务中引 Jar 包即可。
可以理解为:spring.factories
文件,用来记录项目包外需要注册的 bean 类名。
实现原理
spring-core
包里定义了SpringFactoriesLoader
类,这个类实现了检索 META-INF/spring.factories
文件,并获取指定接口的配置的功能。
在这个类中定义了两个对外的方法:
loadFactories
根据接口类获取其实现类的实例,这个方法返回的是对象列表loadFactoryNames
根据接口获取其接口类的名称,这个方法返回的是类名的列表
在方法中会遍历整个 ClassLoader
中所有 Jar 包下的 spring.factories
文件,也就是我们可以在自己 jar 中配置 spring.factories
文件,不会影响到其他地方的配置,也不会被别人的配置覆盖。
应用
在 Spring Boot 的很多包中都能够找到 spring.factories
文件,如 spring-boot
包:
文件内容:
最后
以上就是粗心羊为你收集整理的【SpringBoot】SPI 与 spring.factories什么是 SPISPI 原理Spring.factories的全部内容,希望文章能够帮你解决【SpringBoot】SPI 与 spring.factories什么是 SPISPI 原理Spring.factories所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复