Skip to main content

什么是自动装配?

作者:程序员马丁

在线博客:https://open8gu.com

note

大话面试,技术同学面试必备的八股文小册,以精彩回答应对深度问题,助力你在面试中拿个offer。

回答话术

Spring 的自动装配,本质上就是一套用于自动加载当前项目和外部依赖中 Spring 配置类或组件的机制。这个机制基于通过启动类上 @EnableAutoConfiguration 注解引入的两个核心组件 AutoConfigurationImportSelectorAutoConfigurationPackages.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 机制自动引入外部组件,与自动扫描启动类所在包与子包中的组件。