Spring Boot自动装配(必面问题详解)

本文基于Spring Boot 2.6.3,在文章最后总结了面试回答的核心要点。

1. 什么是自动装配

我们在使用Spring Boot的时候,会自动将Bean装配到IoC容器中。例如我们在使用Redis数据库的时候,会引入依赖spring-boot-starter-data-redis。在引入这个依赖后,服务初始化的时候,会将操作Redis需要的组件注入到IoC容器中进行后续使用。自动装配大致过程如下:

(1)获取到组件(例如spring-boot-starter-data-redis)META-INF文件夹下的spring.factories文件。
(2)spring.factories文件中列出需要注入IoC容器的类。
(3)将实体类注入到IoC容器中进行使用。

2. 自动装配原理

上一章中的大致流程,是通过@SpringBootApplication进行实现。这个注解声明在Spring Boot的启动类上。

@SpringBootApplication
public class DailySpringApplication {
    public static void main(String[] args) {
        SpringApplication.run(DailySpringApplication.class, args);
    }
}

点击进入@SpringBootApplication注解,其中通过@EnableAutoConfiguration注解实现了自动装配。

@Target({ElementType.TYPE}) // 该注解作用于接口、类、枚举
@Retention(RetentionPolicy.RUNTIME) // 注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在
@Documented // 有了该注释后,如果有接口使用了该注解,生成的javadoc文件中,会把该注解展示出来
@Inherited // 如果一个类用上了@Inherited修饰的注解,那么其子类也会继承这个注解
@SpringBootConfiguration // 标识它是一个Spring Boot配置类
@EnableAutoConfiguration // 主要是通过这个注解实现自动装配
@ComponentScan(  // 配置类上添加 @ComponentScan 注解。该注解默认会扫描该类所在的包下所有的配置类
    excludeFilters = {@Filter(
    type = FilterType.CUSTOM,
    classes = {TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {AutoConfigurationExcludeFilter.class}
)}
)

点击进入@EnableAutoConfiguration注解,AutoConfigurationPackage的作用是记录使用了该注释的类所在的包以及子包的路径,以便后序读取,可以参考这篇文章。其中,最重要的是AutoConfigurationImportSelector.class,将需要装配的类装配到IoC容器中,下面我们重点分析一下这个类的实现。

@Target({ElementType.TYPE}) // 该注解作用于接口、类、枚举
@Retention(RetentionPolicy.RUNTIME) // 注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在
@Documented // 有了该注释后,如果有接口使用了该注解,生成的javadoc文件中,会把该注解展示出来
@Inherited // 如果一个类用上了@Inherited修饰的注解,那么其子类也会继承这个注解
@AutoConfigurationPackage // 记录使用了该注释的类所在的包以及子包的路径,以便后序读取
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration{...}

3. 核心类分析

AutoConfigurationImportSelector中的selectImport是自动装配的核心实现,它主要是读取META-INF/spring.factories文件,经过去重、过滤,返回需要装配的配置类集合。

public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        } else {
            AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
            return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
        }
    }

我们点进getAutoConfigurationEntry()方法:

  • getAttributes获取@EnableAutoConfiguration中的exclude、excludeName等。
  • getCandidateConfigurations获取所有自动装配的配置类,也就是读取spring.factories文件,后面会再次说明。
  • removeDuplicates去除重复的配置项。
  • getExclusions根据@EnableAutoConfiguration中的exclude、excludeName移除不需要的配置类。
  • fireAutoConfigurationImportEvents 广播事件

最后根据多次过滤、判重返回配置类合集。

protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
        if (!this.isEnabled(annotationMetadata)) {
            return EMPTY_ENTRY;
        } else {
            AnnotationAttributes attributes = this.getAttributes(annotationMetadata); 
            List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
            configurations = this.removeDuplicates(configurations);
            Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
            this.checkExcludedClasses(configurations, exclusions);
            configurations.removeAll(exclusions);
            configurations = this.getConfigurationClassFilter().filter(configurations);
            this.fireAutoConfigurationImportEvents(configurations, exclusions);
            return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
        }
    }

我们点进getCandidateConfigurations()方法,这里通过loadFactoryNames方法,扫描classpath下的META-INF/spring.factories文件,里面是以key=value形式存储,我们读取其中key=EnableAutoConfiguration,value就是需要装配的配置类,也就是getCandidateConfigurations返回的值。

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
     List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
     Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.");
     return configurations;
}

4. 核心总结

(1)通过注解@SpringBootApplication=>@EnableAutoConfiguration=>@Import({AutoConfigurationImportSelector.class})实现自动装配。
(2)AutoConfigurationImportSelector类中重写了ImportSelector中selectImports方法,批量返回需要装配的配置类。
(3)通过Spring提供的SpringFactoriesLoader机制,扫描classpath下的META-INF/spring.factories文件,读取需要自动装配的配置类。
(4)依据条件筛选的方式,把不符合的配置类移除掉,最终完成自动装配。


Ref:
1.《Spring Cloud Alibaba 微服务原理与实战》谭锋
2.《Spring 5 核心原理与30个类手写实战》谭勇德

  • 6
    点赞
  • 39
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值