【SpringBoot】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 包:
在这里插入图片描述
文件内容:
在这里插入图片描述

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Boot中,SPI(Service Provider Interface)是一种机制,用于在应用程序中动态加载和实例化服务提供者的实现类。SPI的使用可以通过在配置文件中定义接口和实现类的映射关系来实现。在Spring Boot中,可以使用spring.factories文件来定义SPI的配置。 在Spring Boot中,如果我们想要让SpiTestConfig加载,我们可以在resource目录下添加一个META-INF文件夹,并在其中添加一个名为spring.factories的配置文件。在spring.factories文件中,我们可以使用org.springframework.boot.autoconfigure.EnableAutoConfiguration键来指定要自动加载的配置类,例如config.SpiTestConfig。这样,当Spring Boot启动时,它会自动加载并实例化SpiTestConfig配置类。 举个例子,假设我们有一个名为SpiTestConfig的配置类,我们可以在spring.factories文件中添加以下内容: ``` org.springframework.boot.autoconfigure.EnableAutoConfiguration=config.SpiTestConfig ``` 这样,当Spring Boot启动时,它会自动加载并初始化SpiTestConfig配置类,并执行其中的逻辑。 总结起来,Spring Boot中的SPI机制可以通过在配置文件中定义接口和实现类的映射关系来实现动态加载和实例化服务提供者的实现类。在Spring Boot中,我们可以使用spring.factories文件来定义SPI的配置,以实现自动加载和初始化配置类。 #### 引用[.reference_title] - *1* *2* [SpringBoot spi](https://blog.csdn.net/qq_42651904/article/details/116091149)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [SPI 及其在 Springboot 中的使用](https://blog.csdn.net/weixin_44512127/article/details/127977729)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值