一、前言
SpringBoot 的诞生,简化了 Spring 应用程序的初始设置和开发过程,使开发人员可以更快速、更便捷地构建和运行 Spring 应用程序。相信大家在使用或学习 SSM 整合开发 Spring 应用程序时,会感受到写一堆 XML 配置文件的繁琐,即便是基于注解的配置,也无法避免一些重复而冗余的工作。而 SpringBoot 的出现,解决了这一问题,使得开发者可以更专注于业务逻辑的开发。
Spring Boot采用了"约定优于配置 "的理念,大大减少了开发者的配置工作。它提供了一种自动装配机制,即自动的将 Spring 应用程序及其所需的依赖关系进行绑定和装配,如引入了spring-boot-starter-web
依赖后,我们无需写 web 相关的 xml 或者配置类,就拥有了 web 环境,这就得益于 SpringBoot 的自动装配机制。 但是,Spring Boot 的自动装配并不是黑箱操作,它的工作原理是开放透明的。通过理解 Spring Boot 的自动装配原理,我们可以更好地利用这个框架,更灵活地定制和扩展我们的应用程序,我们也能创作出自定义的 starter。
那么在接下来的内容中,我将详细介绍Spring Boot的自动装配原理,希望能够帮助到大家。
二、从启动类出发
还是以 web 环境为例,当引入spring-boot-starter-web
依赖后,我们仅需在启动类上加上@SpringbootApplication 注解后,web相关的配置就生效了,也就有了 web 环境。那我们就来研究一下@SpringbootApplication 注解为我们做了什么吧。
@SpringbootApplication 其实是一个组合注解。除了元注解外,还有有3个注解,分别是@SpringBootConfiguration
、@EnableAutoConfiguration
、@ComponentScan
。@SpringbootApplication的定义如下:
java
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
excludeFilters = {@Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {...}
@SpringBootConfiguration
表示当前类是 Spring Boot 应用程序的主配置类,他的本质其实就是@Configuration。所以理论上你可以将所有的 @Configuration 替换为 @SpringBootConfiguration ,因为 @SpringBootConfiguration 是 @Configuration 的一个特殊形式,只是我们约定只有 Spring Boot 应用程序的主配置类即启动类使用@SpringBootConfiguration。@ComponentScan
和单 Spring 环境中的 @ComponentScan 注解是一样的,Spring 会从声明该注解的类的包开始,扫描该包及其子包。注意这里排除了 @AutoConfiguration 声明的 Bean,别不小心写错了导致被排除。@EnableAutoConfiguration
表示开启自动配置。而今天我们的主题就是探讨SpringBoot的自动配置,也就是研究 @EnableAutoConfiguration 的原理。
三、@EnableAutoConfiguration
@EnableAutoConfiguration 的定义如下:
java
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {...}
@AutoConfigurationPackage
:它会注册一个AutoConfigurationPackages.Registrar
Bean,这个 Bean 会将 @EnableAutoConfiguration 注解所在的类的包路径注册为自动配置的基础路径(basePackage
)。这个 basePackage 可以被用的其他配置类中使用。比如spring-boot-starter-data-jpa
就会将这个basePackage设置为实体类的扫描路径。@Import({AutoConfigurationImportSelector.class})
:这个注解导入了AutoConfigurationImportSelector
,而 AutoConfigurationImportSelector 就是自动装配的最终实现。
AutoConfigurationImportSelector 实现了 ImportSelector
接口,ImportSelector 的 selectImports
方法返回一个字符串数组,这个数组中的每个字符串都是一个要导入的配置类的全类名。
那接下来,我们看看 AutoConfigurationImportSelector 是怎么实现 selectImports 这个方法吧。
java
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
// 如果自动配置没有开启,返回空数组
return NO_IMPORTS;
} else {
// 核心:调用 getAutoConfigurationEntry 方法找到 AutoConfigurationEntry
AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
// 返回配置类集合
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
}
protected static class AutoConfigurationEntry {
// 配置类集合
private final List<String> configurations;
// 需要排除的类集合
private final Set<String> exclusions;
// ...
}
核心是调用 getAutoConfigurationEntry
方法找到 AutoConfigurationEntry
,进入 getAutoConfigurationEntry 方法
java
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
// 如果自动配置没有开启,返回空的 AutoConfigurationEntry
return EMPTY_ENTRY;
} else {
// 获取 @EnableAutoConfiguration 注解的属性
AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
// 核心:获取所有候选的配置类
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
// 移除重复的配置类
configurations = this.removeDuplicates(configurations);
// 获取所有的排除项,即那些在 @EnableAutoConfiguration 注解的 exclude 属性中指定的类
Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
// 检查排除项,如果排除项存在但并不在配置类当中,也就是用户错误的配置了排除项,抛出异常
this.checkExcludedClasses(configurations, exclusions);
// 移除所有的排除项
configurations.removeAll(exclusions);
// 对配置类进行过滤,有的配置类会有条件注解,只保留那些实际需要进行自动配置的类。
configurations = this.getConfigurationClassFilter().filter(configurations);
// 触发自动配置导入事件。
this.fireAutoConfigurationImportEvents(configurations, exclusions);
// 创建并返回一个新的AutoConfigurationEntry,设置所有需要自动配置的类和排除的类。
return new AutoConfigurationEntry(configurations, exclusions);
}
}
核心是调用getCandidateConfigurations
找到所有候选的配置类,进入 getCandidateConfigurations 方法
java
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = new ArrayList<>(
// 核心
SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader())
);
// 核心
ImportCandidates.load(AutoConfiguration.class, getBeanClassLoader()).forEach(configurations::add);
Assert.notEmpty(configurations,
"No auto configuration classes found in META-INF/spring.factories nor in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you "
+ "are using a custom packaging, make sure that file is correct.");
return configurations;
}
这里主要有两个方法,SpringFactoriesLoader.loadFactoryNames
、ImportCandidates.load
,这两个方法就不全部拿出来了,简化出核心。
SpringFactoriesLoader.loadFactoryNames的核心:
java
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
String factoryTypeName = factoryType.getName();
// 加载 SpringFactories 并且拿到 key 为 factoryTypeName 的配置类
return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
// 从类路径下获取 FACTORIES_RESOURCE_LOCATION 资源
Enumeration<URL> urls = classLoader.getResources("META-INF/spring.factories");
// 组装成 map 返回
return result;
}
也就是从类路径下的META-INF/spring.factories
文件中加载所有的配置,其中factoryTypeName
是 org.springframework.boot.autoconfigure.EnableAutoConfiguration
。也就是说,META-INF/spring.factories
文件中org.springframework.boot.autoconfigure.EnableAutoConfiguration
后面的值(配置类的类名)将作为配置类自动装配。
ImportCandidates.load 的核心:
java
ClassLoader classLoaderToUse = decideClassloader(classLoader);
String location = String.format("META-INF/spring/%s.imports", annotation.getName());
Enumeration<URL> urls = findUrlsInClasspath(classLoaderToUse, location);
这个 annotation 是 @AutoConfiguration
,那么这个 location 就是META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration
,也就是从类路径下的META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
文件中加载所有的配置类。
其实到这我们已经知道找到底了,就是从类路径下的META-INF/spring.factories
和META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
文件中加载所有的配置类,也就实现了自动配置。
说明:我的 sprignboot 版本是 2.7.18,如果使用在这之前的版本,可能只能从
META-INF/spring.factories
加载所有的配置类,从最后的getCandidateConfigurations
方法注释上可以看到:出于向后兼容的原因,它还将考虑SpringFactoriesLoader和getSpringFactoriesLoaderFactoryClass() 。即兼容从META-INF/spring.factories
加载配置类。大胆猜测:
META-INF/spring.factories
不仅仅是定义自动配置类的,他可以写上各种工厂,如ApplicationContextInitializer
、ApplicationListener
,所以在后来版本中,定义自定装配的类写在META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
文件中。
四、spring-boot-autoconfigure
现在我们知道了自动配置原理了,那赶紧看看spring-boot-starter-web
有没有spring.factories
或者org.springframework.boot.autoconfigure.AutoConfiguration.imports
文件吧。当你兴冲冲地点开 jar 包,很遗憾,没有。

啊?我们咔咔咔分析了一大堆,这骗我呢?
没有骗你,我们知道,引入spring-boot-starter-web
,其实它也引入了其他的依赖,比如spring-web
、 spring-boot-starter-tomcat
,以及spring-boot-starter
。而spring-boot-starter
又引入了spring-boot-autoconfigure
。
spring-boot-autoconfigure
模块是实现 Spring Boot 自动配置功能的关键,它包含了大量的自动配置类,用于自动配置Spring应用的各种功能和组件。
可以看到,Spring 官方已经对各种各样的场景写好了配置类,当你引入某个 starter 如kafka,那对应的 kafka的配置类就会生效。而这个配置类能自动配置,确实是因为我们前面分析的那样,spring-boot-autoconfigure
的类路径下存在spring.factories
或者org.springframework.boot.autoconfigure.AutoConfiguration.imports
文件。

在这个文件中,定义了需要自动装配的配置类。总共144个:

五、自定义starter
知道原理后,就能自定义 starter 了。我们就做一个最简单的,让 SpringBoot 帮我自动装配一个 User 和一个 Dog,这个 User 和 Dog 对象只有 name、age 属性。
第一步:编写配置类。
java
@AutoConfiguration
public class UserAutoConfiguration{
@Bean
public User user(){
return new User("小小", 18);
}
}
@AutoConfiguration
public class DogAutoConfiguration{
@Bean
public Dog dog(){
return new Dog("大大", 2);
}
}
第二步:编写自动装配的文件,上面说到 Spring 做了兼容,所以两种文件都可以,二选一即可,即使两个都写 Spring 也会帮你去重。
- spring.factories
arduino
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.example.starter.config.UserAutoConfiguration,\
org.example.starter.config.DogAutoConfiguration
- org.springframework.boot.autoconfigure.AutoConfiguration.imports
arduino
org.example.starter.config.UserAutoConfiguration
org.example.starter.config.DogAutoConfiguration
第三步:将这个模块打成 jar 包 第四步:引入这个 jar 包并启动容器
java
@SpringBootApplication
public class SystemApplication {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SystemApplication.class);
User user = context.getBean(User.class);
Dog dog = context.getBean(Dog.class);
System.out.println(user);
System.out.println(dog);
}
}
这样就完成了自定义 starter。
六、结束语
最后,愿你我都有自由的灵魂。