likes
comments
collection
share

spring.factories自动配置

作者站长头像
站长
· 阅读数 15

WangScaler: 一个用心创作的作者。

声明:才疏学浅,如有错误,恳请指正。

前言

我们知道springboot之于ssm的优势之一就是自动配置,启动的时候会将@SpringBootApplication 所在的Application类所在的包下所有的@component注解所标记的bean都注册到spring容器中。

在ssm中,我们需要在xml配置很多项,才能扫描到我们的bean,而springboot我们只需要简简单单加个注解即可完成。像我们最常见的注解@Service、@RestController等都包含了@component注解,所以运行的时候会自动注册到spring容器中。

然而在我们项目中有多个模块,那么其他模块的bean是不是就无法注册以及使用了呢?

实操演练

为了方便的管理各个模块,我将redis单独的做了一个模块,而业务在使用的时候提示

Description:
A component required a bean of type 'com.wangscaler.chatredis.service.RedisService' that could not be found.
Action:
Consider defining a bean of type 'com.wangscaler.chatredis.service.RedisService' in your configuration.

也就是找不到这个文件,咱们上边说了启动的时候会扫描Application类所在的包下所有的@component注解,而我们的RedisService明显不在我们的包下。如图

spring.factories自动配置 那么我们该怎么解决这个问题呢?

使用@Import注解实现

我们只需要在启动类上使用@Import注解

package com.wangscaler.chatuser;

import com.wangscaler.chatredis.service.RedisService;

@SpringBootApplication
@Import({ RedisService.class })
public class ChatUserApplication {
    public static void main(String[] args) {
        try {
            SpringApplication.run(ChatUserApplication.class, args);
        } catch (Exception e) {
            System.out.println(e);
        }
    }
}

即可。

使用spring.factories实现

我们在redis模块下的resources\META-INF\spring.factories文件中配置以下代码即可

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.wangscaler.chatredis.service.RedisService

值得注意的是,在springboot2.7之后这种使用方法将被弃用,那么弃用之后改怎么来解决这个问题呢?

弃用spring.factories之后

弃用之后我们只需要在resources文件夹下创建文件org.springframework.boot.autoconfigure.AutoConfiguration.imports,同样还是和spring.factories配置一样将我们的全限定性名写到文件里即可com.wangscaler.chatredis.service.RedisService

原理解读

我们上述使用的方法是利用了spring的SPI机制,那么为什么我们在这个文件中配置了这个方法,他就能自动注册呢?

我们打开我们的启动类,找到@SpringBootApplication这个注解,我们直接点进去看他的源码,我们发现这个类上边使用了一个@EnableAutoConfiguration的注解,如下图

spring.factories自动配置

我们继续深入,点进这个注解的源码,我们看到一个@Import注解,如下图。

spring.factories自动配置 使用方法和我们的第一种注册bean的方法一样,只不过他这里注册的AutoConfigurationImportSelector的类,那这个类是干什么的呢?我们继续点进去,看到如下源码

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    List<String> configurations = ImportCandidates.load(AutoConfiguration.class, this.getBeanClassLoader()).getCandidates();
    Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you are using a custom packaging, make sure that file is correct.");
    return configurations;
}

我们看到它通过ImportCandidates.load()加载了AutoConfiguration,加载完之后,下边的断言写的很清楚

No auto configuration classes found in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you are using a custom packaging, make sure that file is correct.

翻译过来就是

在META-INF/spring/org.springframework.boot.AutoConfiguration.AutoConfiguration.imports中找不到自动配置类。如果您使用的是自定义打包,请确保该文件是正确的。

那么我们找到他的源码看看他怎么加载的。

public static ImportCandidates load(Class<?> annotation, ClassLoader classLoader) {
    Assert.notNull(annotation, "'annotation' must not be null");
    ClassLoader classLoaderToUse = decideClassloader(classLoader);
    String location = String.format("META-INF/spring/%s.imports", annotation.getName());
    Enumeration<URL> urls = findUrlsInClasspath(classLoaderToUse, location);
    ArrayList importCandidates = new ArrayList();

    while(urls.hasMoreElements()) {
        URL url = (URL)urls.nextElement();
        importCandidates.addAll(readCandidateConfigurations(url));
    }

    return new ImportCandidates(importCandidates);
}

他恰恰就是获取的我们写的org.springframework.boot.autoconfigure.AutoConfiguration.imports这个文件。通过循环将我们写的每一个类加载到importCandidates中。当然这是springboot3中的代码,在springboot2.7中的代码如下图

spring.factories自动配置 从这我们也能看出,他获取的是"META-INF/spring.factories",当然2.7也开始引入3的写法,这里就不贴相关的代码了。从此也可以验证在spring2.7之后开始引入新的写法,开始弃用,但未完全弃用,两种做了兼容,而在springboot3中之前的方法完全被弃用了。

题外话

上述提到的项目是开源项目聊天室的springcloud版本,开源地址,欢迎共建。