什么是自动装配?
作者:程序员马丁
在线博客:https://open8gu.com
大话面试,技术同学面试必备的八股文小册,以精彩回答应对深度问题,助力你在面试中拿个offer。
回答话术
Spring 的自动装配,本质上就是一套用于自动加载当前项目和外部依赖中 Spring 配置类或组件的机制。这个机制基于通过启动类上 @EnableAutoConfiguration
注解引入的两个核心组件 AutoConfigurationImportSelector
和 AutoConfigurationPackages.Registrar
实现。
当项目启动以后,AutoConfigurationImportSelector 会通过 SpringFactoriesLoader 去扫描所有 JAR 包的 META-INF/spring.factories 文件,然后根据文件内指定的类全限定名去加载对应的组件或配置类。我们默认引入的 spring-boot-autoconfigure
模块的 spring.factories
文件默认就包含了绝大数官方 Starter 所需的配置项。
然后是AutoConfigurationPackages.Registrar
,在项目启动后,它将会扫描引入它的注解所在类 —— 通常就是启动类本身 —— 所在包与所有子包下的配置类和组件,并将其加载到容器中,这也是“启动类要放到最外层包下”这个潜规则的由来。不同于 AutoConfigurationImportSelector
,它主要是自动加载项目的内部组件和配置。
问题详解
1. @EnableAutoConfiguration
在 ✅ @SpringBootApplication 注解是怎么生效的?这篇文章中,我们提到 @SpringBootApplication
注解包含了一个 @EnableAutoConfiguration
注解。
而该注解则引入了两个用于实现自动装配功能的核心组件:
- 直接通过
@Import
元注解注解引入了自动装配选择器 AutoConfigurationImportSelector 。 - 通过
@AutoConfigurationPackage
注解(上的@Import
注解)间接引入了基础包配置注册器 AutoConfigurationPackages.Registrar。
简单的来说,前者用于扫描 META-INF/spring.factories
路径下的外部配置类,用于对外,而后者用于扫描用户启动类所在包路径下的配置类,用于对内。
注意,在更早或更晚的 SpringBoot 版本中,
@EnableAutoConfiguration
注解引入可能是ImportAutoConfigurationImportSelector
或者EnableAutoConfigurationImportSelector
,不过它们都是AutoConfigurationImportSelector
的子类,所以从功能上来说都是一样的。
2. AutoConfigurationImportSelector
AutoConfigurationImportSelector
的主要功能,就是在项目启动后扫描所有包的 META-INF/spring.factories
文件中的指定的配置类或组件类,并将其纳入 Spring 管理。
它实质上是 Spring 提供的一套 SPI 机制,Starter 开箱即用的效果全部要归功于它。
2.1. ImportSelector
AutoConfigurationImportSelector
实现了 ImportSelector
接口,因此可以在应用启动的过程中,向 Spring 注册额外的配置类:
public interface ImportSelector {
// 要导入到 spring 中的类全限定名
String[] selectImports(AnnotationMetadata importingClassMetadata);
// 过滤器,用于确认哪些类不需要导入
@Nullable
default Predicate<String> getExclusionFilter() {
return null;
}
}
该接口除了一个返回待引入类的全限定名的方法外,还额外提供了一个用于筛选引入类的过滤器,因此该即接口可以做到“选择性引入”。
不过,AutoConfigurationImportSelector 实现的实际上是 ImportSelector
的子接口 DeferredImportSelector
,它在前者的基础上实现了延迟装配,好各种基于 @Conditional
注解的条件装配生效。
关于
@Conditional
注解,请参见 ✅ Spring 有哪些常用注解?
2.2. SpringFactoriesLoader
我们都知道,JDK 本身有一提供一套基于 ServiceLoader
的 SPI 机制,而 Spring 的自动装配本质上也是一套 SPI 机制。
Spring 有一个名为 SpringFactoriesLoader
的静态工具类,它等效于一个只扫描 META-INF/spring.factories
路径的 ServiceLoader
,用于配合 Spring 本身的类加载机制实现动态加载配置或组件的效果。
当我们在 META-INF/
文件夹中提供一个 spring.factories 文件以后,只需要按如下格式提供类的全限定名:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration
在启动后, AutoConfigurationImportSelector
就会通过 SpringFactoriesLoader
组件读取它:
/**
* The location to look for factories.
* <p>Can be present in multiple JAR files.
*/
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
// 启动后只读取一次,后续直接走缓存
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) {
return result;
}
try {
// 获取资源 URL,这里拿到的是所有 JAR 包的 spring.factories 文件
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
result = new LinkedMultiValueMap<>();
while (urls.hasMoreElements()) {
// 从 spring.factories 文件中读取到需要加载的类全限定名
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
// 注意,这里的全限定名会按 factoryType 分组
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryTypeName = ((String) entry.getKey()).trim();
for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
result.add(factoryTypeName, factoryImplementationName.trim());
}
}
}
// 加载完毕后添加到缓存
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
需要注意的是,
META-INF/spring.factories
这个路径在 SpringBoot 3.0 以后改为了META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
。
2.3. factoryType
此外,在 spring.factories 文件中,我们会注意到,里面文章中的类全限定名们不是一个列表,而是按特定 Key 分组的 Map<String, List<String>>
的格式。
这里的 Key 在 Spring 中被称为 factoryType。它们通常就是 SpringBoot 中各种关键组件和注解的全限定名,SpringBoot 会根据组件或配置类所属的 factoryType 的不同,而在不同的时机加载并使用它们。
比如 org.springframework.boot.autoconfigure.EnableAutoConfiguration
,实际上就是 @EnableAutoConfiguration
注解的全限定名,在源码中也是直接同构 XXX.class
这种方式直接获取的:
// AutoConfigurationImportSelector
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
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;
}
// 直接返回 @EnableAutoConfiguration 直接的类型
protected Class<?> getSpringFactoriesLoaderFactoryClass() {
return EnableAutoConfiguration.class;
}
比如,这里举出 spring-boot-autoconfigure 的 spring.factories 中的部分内容:
# 初始化器
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
# SpringBoot 事件监听器
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer
# 导入监听器
org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener
# 导入过滤器,也就是各种 @ConditionXXX 注解的实现
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnBeanCondition,\
org.springframework.boot.autoconfigure.condition.OnClassCondition,\
org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition
# 自动配置类,官方提供的 Starter 有九成的配置类在这里
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration,\
这里我们重点关注一下这三个 Key:
- EnableAutoConfiguration:自动配置类,官方提供的 Starter 有九层以上的配置类都在这里。
- ApplicationListener:Spring 事件监听器,在这里注册的监听器还能监听到一些普通监听器监听不到的 SpringBoot 应用的生命周期事件。
- AutoConfigurationImportFilter:实现了
Condition
接口的装配条件,SpringBoot 中的各种@ConditionalOnXXX
注解就依赖这里引入的组件实现。
当然,SpringBoot 里面还有更多的 factoryType,比如 MyabtisPlus 的 spring.factories 文件里就额外的引入了一个 EnvironmentPostProcessor
:
# 配置文件后处理器,由 ConfigFileApplicationListener 处理
org.springframework.boot.env.EnvironmentPostProcessor=\
com.baomidou.mybatisplus.autoconfigure.SafetyEncryptProcessor
# MybatisPlus 的配置类
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.baomidou.mybatisplus.autoconfigure.MybatisPlusLanguageDriverAutoConfiguration,\
com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration
如果有兴趣的话可以关注一下平时用的各种第三方库是怎么做的。
注意,在 2.7.0 以后,springboot 放弃了 spring.factories 文件,改为使用
org.springframework.boot.autoconfigure.AutoConfiguration.imports
。在这个新的配置文件里面,不再需要 factoryType 了(显然 spring 自己也觉得还要记忆 factoryType 非常不方便),直接一行一行的写上所需要的自动配置类的类全限定名即可。
甚至更进一步,基于 Spring 自定义的 AnnotationProcessor,你只需要将
@Configuration
注解换成@AutoConfiguration
,Spring 就会在编译时自动帮你生成好 SPI 文件。
3. AutoConfigurationPackages.Registrar
这个组件本质上是一个 ImportBeanDefinitionRegistrar
,在项目启动时,它将扫描在 @AutoConfigurationPackage 注解中指定的包路径,然后将将路径下的配置类或者组件类注册到 Spring 容器。
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
// 默认情况下,metadata 其实就是启动类的元数据
// 因此在此时扫描的就是启动类所在的包及其子路径
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
register(registry, new PackageImport(metadata).getPackageName());
}
@Override
public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new PackageImport(metadata));
}
}
AnnotationMetadata
是指 @AutoConfigurationPackage
注解所在类的元注解信息,更简单点来说,你可以理解这里的 AnnotationMetadata
实际上就是启动类上的 @AutoConfigurationPackage
注解。
默认情况下,除了启动类上的 @SpringBootApplication
以外,我们不会再在其他的地方添加 @AutoConfigurationPackage
注解了,因此它总是会扫描启动类所在包下的所有路径,这就是“启动类要放到最外层的包”这个潜规则的由来。
可见,如果说 AutoConfigurationImportSelector
是用来使外部配置自动生效,那 AutoConfigurationPackages.Registrar
就是用来使内部配置自动生效。
关于
@Import
注解与ImportBeanDefinitionRegistrar
的作用,请参见:@Import 注解是怎么生效的?
4. 总结加载外部组件的几种方式
现在,我们理解了 Spring 的自动装配,因此不妨再回头重新梳理一下 SpringBoot 中装配外部依赖中的组件的几种方式:
- 纯手动:通过
@Configuration
+@Bean
的方式在自己的项目中全手动配置。 - 半自动:直接或者间接通过
@Import
注解将第三方组件引入,或通过@ComponentScan
扫描第三方组件所在的包路径。 - 全自动:基于 Spring 的 SPI 机制,也就是 spring.factories 文件实现自动装配。
现在再看看 @SpringBootApplication
注解,我们也就理解它的组成了:
@Configuration
:代表纯手动装配,我们可以直接在启动类里面通过带@Bean
的工厂方法装配。@ComponentScan
:代表半自动装配,我们可以指定扫描第三方组件所在包路径,将其批量引入。@EnableAutoConfiguration
:代表全自动装配,基于 SPI 机制自动引入外部组件,与自动扫描启动类所在包与子包中的组件。