我是靠谱客的博主 粗心羊,最近开发中收集的这篇文章主要介绍【SpringBoot】SPI 与 spring.factories什么是 SPISPI 原理Spring.factories,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

目录

  • 什么是 SPI
  • SPI 原理
  • Spring.factories
    • 实现原理
    • 应用

什么是 SPI

Service Provider Interface。是 JDK 内置的一种服务提供发现机制,为某个接口寻找服务的实现,在模块化设计中这个机制很重要。

下面以一个简单的 demo 来示意 SPI 的用法:

  1. 首先定义一个接口
public interface SPIService {
    void execute();
}
  1. 我们在两个模块中实现这个接口:
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()");
    }
}
  1. 最后需要在 MTEA-INF/services 路径下创建一个配置文件:
    文件名是接口名
    在这里插入图片描述
    文件内容是实现类的全限定名
com.test.spi.SpiImpl1
com.test.spi.SpiImpl2
  1. 执行:
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();
        }
    }
}
  1. 输出
SpiImpl1.execute()
SpiImpl2.execute()
SpiImpl1.execute()
SpiImpl2.execute()

可以看到,我们首先需要获取到接口的所有实现类,即服务提供者 providers,然后依次调用其实现的接口方法,即可实现所有服务提供者的调用。

SPI 原理

  1. 构建新的 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

  1. 获取其 Iterator 迭代器
  Iterator<SPIService> iterator = load.iterator();
  1. hasNext,是否还有元素
 while (iterator.hasNext())
  • LazyIteratorhasNext。 扫描所有 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;
        }

  1. 获取下一个元素
 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所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(55)

评论列表共有 0 条评论

立即
投稿
返回
顶部