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个类手写实战》谭勇德