SpringBoot3自动装配原理

SpringBoot版本: 3.2.0

什么是自动装配

装配的是什么?

装配的就是 bean, 也就是可以将一个 对象交给 Spring 容器管理

为什么可以自动?

SpringBoot 秉持的就是 约定优于配置,所以要想做到就得有一定的规则,比如

  • 总得要告诉 Spring 我们想要装配的是什么类,通过什么方式告诉呢? 就是将想要注入的类集中写在一个固定位置的配置文件中
    • 在 2.7.0 之前使用的配置文件名称是 spring.factories, 这个文件的位置是 META-INF/spring.factories
    • 在 2.7.0 之后使用的配置文件名称是 org.springframework.boot.autoconfigure.AutoConfiguration.imports, 这个文件的位置是 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
  • 可以认为配置文件是开发者需要提供的,当然 SpringBoot 内部也使用了这种机制,既然提供了配置文件,那么 Spring 框架就需要按照约定的位置去找这些类了,这个在 SpringBoot 中是通过 @Import 注解导入了一个 DeferredImportSelector 实现类来实现的,这个实现类是 AutoConfigurationImportSelector
  • 在 SpringBoot 中还提供了一种机制,就是通过一系列的条件判断我们在配置文件中写的这些类是否真的可以导入,这一系列条件是通过 @ConditionalOnxxxx 注解来实现

接下来简单总结一下 SpringBoot3 的自动装配原理

  1. 通过 @Import 导入 AutoConfigurationImportSelector 类, 可以参考 {% series Spring中Import注解源码解析 %}
  2. AutoConfigurationImportSelector 会去找所有 classpath 下所有 jar 下的 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件,将文件里面的全限定类名加载进来
  3. 对加载加来的类进行一系列的条件判断,符合条件的才会真的进行加载到 Spring 容器中

自动装配的示例

SpringBoot3自动装配案例 - 掘金 (juejin.cn)

SpringBoot3 自动装配的流程

SpringBoot 项目启动主类上只有一个 SpringBootApplication 注解,我们看一下这个注解的体系

flowchart LR A["@SpringBootApplication"] style A fill:#8ff600 B["@ComponentScan"] C["@EnableAutoConfiguration"] style C fill:#8ff600 D["@SpringBootConfiguration"] A--> B & C & D E["@Configuration"] D-->E F["@AutoConfigurationPackage"] G["@Import(AutoConfigurationPackages.Registrar.class)"] C-->F F-->G H["@Import(AutoConfigurationImportSelector.class)"] style H fill:#8ff600 C-->H

涉及到自动注入的注解使用绿色标记出来了,所以最终需要看的就是 AutoConfigurationImportSelector, 而这个类是一个 DeferredImportSelector 接口的实现类,具体参考 \[Spring中DeferredImportSelector源码解析], 这里就直接给出 DeferredImportSelector 接口的执行流程

flowchart TB A["Group实现类中的 process() 方法"] B["DeferredImportSelector实现类 中的 selectImports() 方法 (该方法不一定执行,当 Group 实现类中 有调用的时候才会执行,没有调用就没有执行 SpringBoot自动装配时就没有执行 selectImports() 方法)"] C["Group实现类中的 selectImports() 方法 这个方法的返回值才是真正要被 导入的类"] A-->B-->C

先看一下 AutoConfigurationImportSelector 这个类中相关的代码(只展示主流程部分)

java 复制代码
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
		ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {

    // 根据这个方法可以知道当前 DeferredImportSelector 实现类对应的 Group 类是 AutoConfigurationGroup
    // 步骤1: 根据这个方法得到具体的 Group 实现类
	public Class<? extends Group> getImportGroup() {
		return AutoConfigurationGroup.class;
	}

    // 步骤4: 这个方法是从 Group 实现类中的 process() 方法调用过来的
    protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return EMPTY_ENTRY;
		}
		AnnotationAttributes attributes = getAttributes(annotationMetadata);
        // 步骤5: getCandidateConfigurations 方法会从配置文件加载类
		List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
		configurations = removeDuplicates(configurations);
        // 获取手动排除掉的类,比如我们配置了 exclude 相关的配置
		Set<String> exclusions = getExclusions(annotationMetadata, attributes);
		checkExcludedClasses(configurations, exclusions);
		configurations.removeAll(exclusions);
        // 步骤6: 这里就是根据条件判断,过滤掉不符合条件的类
		configurations = getConfigurationClassFilter().filter(configurations);
		fireAutoConfigurationImportEvents(configurations, exclusions);
		return new AutoConfigurationEntry(configurations, exclusions);
	}

    private static class AutoConfigurationGroup
			implements DeferredImportSelector.Group, BeanClassLoaderAware, BeanFactoryAware, ResourceLoaderAware {
        
        // 步骤2: 调用 Group 实现类中的 process() 方法
        public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
            // 步骤3: 调用 AutoConfigurationImportSelector 类的 getAutoConfigurationEntry() 方法
            // 所以这里并没有调用 AutoConfigurationImportSelector 类的 selectImports() 方法
			AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
				.getAutoConfigurationEntry(annotationMetadata);
			this.autoConfigurationEntries.add(autoConfigurationEntry);
			for (String importClassName : autoConfigurationEntry.getConfigurations()) {
				this.entries.putIfAbsent(importClassName, annotationMetadata);
			}
		}
    }
}

步骤1 和 步骤2 的调用在讲解 DeferredImportSelector 接口的时候已经讲过了,这里简单贴一下调用过程

csharp 复制代码
AbstractApplicationContext#refresh
    AbstractApplicationContext#invokeBeanFactoryPostProcessors
        public PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors
            private PostProcessorRegistrationDelegate#invokeBeanDefinitionRegistryPostProcessors
                ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry
                    ConfigurationClassPostProcessor#processConfigBeanDefinitions
                        ConfigurationClassParser#parse
                            for 循环 start[解析单个类]
                                ConfigurationClassParser#parse(AnnotationMetadata, java.lang.String)
                                    ConfigurationClassParser#processImports    (1)
                            for 循环结束
                            DeferredImportSelectorHandler#process              (2)
                                DeferredImportSelectorGroupingHandler#processGroupImports
  1. 在 (1) 位置会找到 DeferredImportSelector 类型的类,然后缓存起来,这个是在 ConfigurationClassParser 的 processImports() 方法中处理的
  2. 所以自定义的类都解析成 BeanDefiniton 之后(上面流程中的 for 循环之后),开始执行 DeferredImportSelectorHandler 类的 process() 方法,这是因为 (1) 中缓存的 DeferredImportSelector 就是存放在 DeferredImportSelectorHandler 类中,也就是在 (2) 中调用到 Group 类的 process() 方法

接下来直接从 步骤3 开始看,

java 复制代码
//  AutoConfigurationGroup类
public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
    // 步骤3
	AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
	.getAutoConfigurationEntry(annotationMetadata);
}

//  AutoConfigurationImportSelector类
// 步骤4: 这个方法是从 Group 实现类中的 process() 方法调用过来的
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
    if (!isEnabled(annotationMetadata)) {
        return EMPTY_ENTRY;
    }
    AnnotationAttributes attributes = getAttributes(annotationMetadata);
    // 步骤5: getCandidateConfigurations 方法会从配置文件加载类
    List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
    configurations = removeDuplicates(configurations);
    // 获取手动排除掉的类,比如我们配置了 exclude 相关的配置
    Set<String> exclusions = getExclusions(annotationMetadata, attributes);
    checkExcludedClasses(configurations, exclusions);
    configurations.removeAll(exclusions);
    // 步骤6: 这里就是根据条件判断,过滤掉不符合条件的类
    configurations = getConfigurationClassFilter().filter(configurations);
    fireAutoConfigurationImportEvents(configurations, exclusions);
    return new AutoConfigurationEntry(configurations, exclusions);
}

步骤5 完成的事情--从配置文件获取数据,得到的是配置的全限定类名

java 复制代码
//  AutoConfigurationImportSelector 类
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    List<String> configurations = ImportCandidates.load(AutoConfiguration.class, getBeanClassLoader())
        .getCandidates();
    return configurations;
}

// ImportCandidates 类
private static final String LOCATION = "META-INF/spring/%s.imports";

public static ImportCandidates load(Class<?> annotation, ClassLoader classLoader) {
    ClassLoader classLoaderToUse = decideClassloader(classLoader);
    //  location = META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
    String location = String.format(LOCATION, annotation.getName());
    //  通过 ClassLoader 读取 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
    // 文件, 这个不是某个 jar 包,而是 classpath 下的所有jar 包对应路径的文件
    // 所以返回的是 url 的集合
    Enumeration<URL> urls = findUrlsInClasspath(classLoaderToUse, location);
    List<String> importCandidates = new ArrayList<>();
    while (urls.hasMoreElements()) {
        URL url = urls.nextElement();
        // 遍历每个 url, 读取对应的文件内容, 然后将文件内容中的内容添加到 importCandidates 中
        importCandidates.addAll(readCandidateConfigurations(url));
    }
    return new ImportCandidates(importCandidates);
}

下面截取 spring-boot-autoconfigure 这个包下 org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件的一部分内容看看

复制代码
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration
......

就是一行一个全限定类名而已,和 SpringBoot2.7 之前的 spring.factories 文件是不同的,这里也拿一个 spring.factories 文件内容对比一下

ini 复制代码
# 这是一个 key
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnBeanCondition,\
org.springframework.boot.autoconfigure.condition.OnClassCondition,\
org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition

# Auto Configure
# 这是自动装配的 key
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
......

步骤6 完成的事情,对 步骤5中获取的全限定类名进行过滤

java 复制代码
//  AutoConfigurationImportSelector类
// 步骤4: 这个方法是从 Group 实现类中的 process() 方法调用过来的
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
    if (!isEnabled(annotationMetadata)) {
        return EMPTY_ENTRY;
    }
    AnnotationAttributes attributes = getAttributes(annotationMetadata);
    // 步骤5: getCandidateConfigurations 方法会从配置文件加载类
    List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
    configurations = removeDuplicates(configurations);
    // 获取手动排除掉的类,比如我们配置了 exclude 相关的配置
    Set<String> exclusions = getExclusions(annotationMetadata, attributes);
    checkExcludedClasses(configurations, exclusions);
    configurations.removeAll(exclusions);
    // 步骤6: 这里就是根据条件判断,过滤掉不符合条件的类
    configurations = getConfigurationClassFilter().filter(configurations);
    fireAutoConfigurationImportEvents(configurations, exclusions);
    return new AutoConfigurationEntry(configurations, exclusions);
}
  1. 调用 getConfigurationClassFilter() 方法获取 classFilter, 这个不跟下去了,直接说结论,其实也是根据 spi 机制从 spring.factories 文件中获取的,从上面列出的 spring.factories 文件中可以看到,这个文件是 key=values 的形式,也就是一个 key 可以配置多个值,而获取 classFilter 的 key 是 AutoConfigurationImportFilter, 再看看 spring.factories 文件中关于 AutoConfigurationImportFilter 的配置
ini 复制代码
# 这个配置文件也是在 spring-boot-autoconfigure 包下面
# Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnBeanCondition,\
org.springframework.boot.autoconfigure.condition.OnClassCondition,\
org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition
  1. getConfigurationClassFilter() 方法的返回值是 ConfigurationClassFilter, 看看这个类,具体的过滤逻辑这里就不再分析
java 复制代码
private static class ConfigurationClassFilter {
	private final AutoConfigurationMetadata autoConfigurationMetadata;
    // 上面讲到会从配置文件中加载到三个 AutoConfigurationImportFilter 实现类,会保存在这里然后调用过滤方法
	private final List<AutoConfigurationImportFilter> filters;
}

解析文件和过滤后的处理

配置文件也解析了,过滤也过滤完了,回过头再看看 Group 类的 process() 方法是从哪里调用过来的,前面有,这里再贴一下流程

csharp 复制代码
AbstractApplicationContext#refresh
    AbstractApplicationContext#invokeBeanFactoryPostProcessors
        public PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors
            private PostProcessorRegistrationDelegate#invokeBeanDefinitionRegistryPostProcessors
                ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry
                    ConfigurationClassPostProcessor#processConfigBeanDefinitions
                        ConfigurationClassParser#parse
                            for 循环 start[解析单个类]
                                ConfigurationClassParser#parse(AnnotationMetadata, java.lang.String)
                                    ConfigurationClassParser#processImports
                            for 循环结束
                            DeferredImportSelectorHandler#process
                                DeferredImportSelectorGroupingHandler#processGroupImports

所以是在 DeferredImportSelectorHandler 的类 process() 方法中处理的

java 复制代码
//  ConfigurationClassParser
public void processGroupImports() {
    for (DeferredImportSelectorGrouping grouping : this.groupings.values()) {
        Predicate<String> exclusionFilter = grouping.getCandidateFilter();
        // 就是在 getImport() 方法中调用到 Group 实现类的 process() 方法
        // 返回值 Group 实现类的 selectImports 方法
        grouping.getImports().forEach(entry -> {
            ConfigurationClass configurationClass = this.configurationClasses.get(entry.getMetadata());
            // 拿到结果之后调用的又是  processImports方法, 这个就是和
            // @Import 导入的是一个普通类的流程一致的
            processImports(configurationClass, asSourceClass(configurationClass, exclusionFilter),
                        Collections.singleton(asSourceClass(entry.getImportClassName(), exclusionFilter)),
                        exclusionFilter, false);
        });
    }
}

//  ConfigurationClassParser
public Iterable<Group.Entry> getImports() {
    for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {
        //  就是在这里调用到 Group 实现类的 process() 方法
        this.group.process(deferredImport.getConfigurationClass().getMetadata(),
                deferredImport.getImportSelector());
    }
    return this.group.selectImports();
}

从上面代码看到,拿到解析和结果的结果之后,通过 foreach 调用 processImports() 方法,这个方法就是处理单个 @Import 导入的类的方法,至此整个流程就串起来了

相关推荐
猿究院_夏燊5 小时前
Spring Bean 生命周期
java·后端·spring
Wang's Blog6 小时前
Java 项目实战: 交友项目:登录链路回顾与今日任务地图
java·开发语言·状态模式·交友
信誓旦旦的程序猿6 小时前
【Python量化实战宝典 #08】可转债与债券接口手册:转股价·溢价率·纯债价值(Python 可跑)
java·python·股票数据api·股票数据api接口·股票api数据接口·股票量化数据api·股票量化数据接口
砚底藏山河6 小时前
python量化:行情数据回溯
java·开发语言·python·金融·maven
Jlzn88886 小时前
华东电芯分选通道数怎么配才不堵线
java·人工智能·架构·自动化·制造
香吧香7 小时前
GRPC连接机制---长连接分析
java·网络与传输协议
CCblandy7 小时前
雪花 ID 四个边界实测:时钟回拨重号 500 个,前端 JSON.parse 直接丢精度
后端·python·算法·性能优化·架构
我是人✓7 小时前
SpringBoot项目的五种创建方式
java·spring boot·后端
Wang's Blog7 小时前
Java 项目实战: 交友项目 - 阿里云 OSS 入门案例:从创建 Bucket 到上传拿到可访问 URL
java·阿里云·交友
裕晟资质规划8 小时前
三类涉密资质延续时限差异分析:军工60日与集成印制3个月的边界
java·服务器·前端·网络·安全