1. 自动装配到底在解决什么问题
先聊一个最基础的痛点。做过传统 Spring 开发的朋友应该都有印象,早年间搭一个 SSM 工程,光是写配置就能耗掉大半天。
web.xml
、
spring-mvc.xml
、
spring-mybatis.xml
、数据源配置、事务管理器配置、扫描路径配置,一个文件漏了,启动直接报
BeanCreationException
,然后就是漫长的排查。
Spring Boot 出来以后,这些配置大部分消失了。你只需要引入一个
spring-boot-starter-web
,再写一个带
@SpringBootApplication
注解的启动类,一个能跑起来的 Web 项目就完成了。背后的功臣就是自动装配机制。
但很多人在面试或者实际工作中,对自动装配的理解停留在"加个注解就能用"的层面。一旦被问到"
@SpringBootApplication
到底做了什么""
@EnableAutoConfiguration
是怎么加载那个自动配置类的""为什么自定义的 starter 能生效",就开始含糊了。
这篇文章把这些事情一次讲透,包括自动装配的核心原理、
AutoConfiguration.imports
文件的加载链路、
@Conditional
条件注解的生效逻辑,以及实际排查自动配置不生效的完整过程,最后再给出一个手写 starter 的案例。
2. 从三类核心注解拆解启动流程
2.1
@SpringBootApplication
是一个组合注解
先看
@SpringBootApplication
的源码定义:
@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:本质上是@Configuration,表明当前类是一个配置类。 -
@EnableAutoConfiguration:自动装配的入口注解,真正开启自动配置加载的逻辑就在这里面。 -
@ComponentScan:默认扫描启动类所在包及其子包下的组件。
需要注意一个容易踩坑的点:
@ComponentScan
默认的扫描范围是启动类所在的包及子包。如果你把一些
@Service
、
@Repository
、
@Controller
放到包外面,除非显式指定
basePackages
,否则不会被扫描到。这也是很多项目启动后接口 404 的常见原因之一。
2.2
@EnableAutoConfiguration
的导入逻辑
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
关键点在于
@Import(AutoConfigurationImportSelector.class)
。
ImportSelector
接口有一个
selectImports(AnnotationMetadata metadata)
方法,Spring 在启动时会把返回的字符串数组中的类名都注册为 Bean 定义。
AutoConfigurationImportSelector
就是通过这个机制把候选的自动配置类批量引入。
2.3
AutoConfigurationImportSelector
做了什么
AutoConfigurationImportSelector
的
getCandidateConfigurations
方法负责加载候选配置类列表:
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 packaging tool that does not support "
+ "META-INF/spring.factories, consider using the "
+ "new AutoConfiguration.imports files.");
return configurations;
}
这里有一个版本差异需要特别注意:
-
Spring Boot 2.7 之前,自动配置类的名单写在
META-INF/spring.factories文件里,key 是org.springframework.boot.autoconfigure.EnableAutoConfiguration。 -
Spring Boot 2.7 开始引入
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件,spring.factories方式仍然兼容,但标记为废弃。 -
Spring Boot 3.0 之后,彻底移除了
spring.factories方式,统一走AutoConfiguration.imports。
所以如果你在网上搜到老文章,照着写
spring.factories
,放在 Spring Boot 3.x 的项目里是不会生效的。这一点会在后面的排查章节详细演示。
3. 自动配置类是怎么被加载的
3.1 配置文件加载优先级
Spring Boot 2.7 之后,加载自动配置类的逻辑集中在
AutoConfigurationImportSelector
里的
getAutoConfigurationEntry
方法:
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
AnnotationAttributes attributes = getAttributes(annotationMetadata);
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
configurations = removeDuplicates(configurations);
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = getConfigurationClassFilter().filter(configurations);
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
整个流程可以概括为四步:
- 获取候选配置类列表。
- 去重。
- 移除显式排除的类。
-
通过
ConfigurationClassFilter做条件过滤,这一步会读取每个自动配置类上的@ConditionalOnXxx注解,判断是否满足条件。
3.2 条件注解在什么时候生效
自动配置类并不是无条件生效的。每个自动配置类上面都有一堆条件注解,比如:
-
@ConditionalOnClass:classpath 下存在指定类才生效。 -
@ConditionalOnMissingBean:容器中不存在指定 Bean 才生效。 -
@ConditionalOnProperty:配置文件中存在指定配置项且值匹配才生效。 -
@ConditionalOnWebApplication:当前是 Web 应用才生效。 -
@ConditionalOnExpression:SpEL 表达式结果为 true 才生效。
举例来说,
DataSourceAutoConfiguration
上面有这些关键注解:
@AutoConfiguration(before = SqlInitializationAutoConfiguration.class)
@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
@ConditionalOnMissingBean(type = "io.r2dbc.spi.ConnectionFactory")
@EnableConfigurationProperties(DataSourceProperties.class)
@Import({
DataSourcePoolMetadataProvidersConfiguration.class,
DataSourceCheckpointRestoreConfiguration.class
})
public class DataSourceAutoConfiguration {
}
意思是:只有当 classpath 下存在
javax.sql.DataSource
和
org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType
时,这个配置类才有资格参与后续判断,并且容器中不能存在
ConnectionFactory
类型的 Bean。
这种"按需加载"的设计让自动装配具备很强的灵活性。引入某个 starter 时,对应的自动配置类进入候选列表;但真正是否创建相关 Bean,还要看当前项目的依赖情况和用户配置。
3.3 自动配置类之间的顺序控制
自动配置类之间也存在依赖关系,例如 Redis 自动配置可能需要先初始化连接工厂,MyBatis 的自动配置需要依赖数据源的自动配置。Spring Boot 提供了两个注解来控制顺序:
-
@AutoConfiguration(after = XxxAutoConfiguration.class):在指定的自动配置类之后执行。 -
@AutoConfiguration(before = XxxAutoConfiguration.class):在指定的自动配置类之前执行。
注意,这个注解是 Spring Boot 2.7 之后引入的。更早的版本里控制顺序用的是
@AutoConfigureAfter
和
@AutoConfigureBefore
。如果项目升级到 2.7+,建议迁移到新注解上,因为老注解在某些场景下会失效。
4. 实战排查:自动配置为什么没生效
4.1 场景一:自定义 starter 在 Spring Boot 3.x 中不生效
之前有读者问过我一个问题:写了一个自定义 starter,把自动配置类配到了
spring.factories
里,在 Spring Boot 2.x 项目里一切正常,但换到 Spring Boot 3.2 之后,配置类一直没有被加载。
这个问题的根因很明确——
spring.factories
方式在 Spring Boot 3.0 里已经被移除了。Spring Boot 3.x 只认
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
文件。
正确做法是在
src/main/resources/META-INF/spring/
目录下新建文件,文件名必须是:
org.springframework.boot.autoconfigure.AutoConfiguration.imports
文件内容每行写一个自动配置类的全限定类名:
com.example.starter.config.MyAutoConfiguration
注意文件编码建议使用 UTF-8,不要写 BOM 头,否则解析时可能出问题。
4.2 场景二:加载了,但 Bean 没创建
配置文件配对了,自动配置类也进入了候选列表,但项目启动后需要用的 Bean 还是提示找不到。这种情况通常是条件注解不满足。
排查思路分三步走:
- 启动时打开自动配置报告。
在
application.yml
中配置:
debug: true
启动后日志中会输出
CONDITIONS EVALUATION REPORT
,里面会列出每个自动配置类的匹配状态:
matched
或
not matched
,并且会说明不匹配的具体原因。
- 确认依赖是否齐全。
例如
RedisAutoConfiguration
要求 classpath 下存在
RedisOperations
类。如果你只是引入了
spring-boot-starter-data-redis
但没有引入对应的客户端库(比如 Lettuce 或 Jedis),条件注解就不会通过。
- 检查是否被用户自定义 Bean 拦截。
@ConditionalOnMissingBean
的意思是"容器中没有这个 Bean 时才生效"。如果你自己定义了一个同类型的 Bean,自动配置就会让位。这其实是 Spring Boot 留给使用者的扩展口子,但有时候也会造成困惑——明明依赖都齐了,自动配置就是不加载,查日志发现是
@ConditionalOnMissingBean
被用户自定义 Bean 挡住了。
5. 手写一个 Spring Boot Starter
单纯讲原理容易飘,实际动手写一个 starter 才能真正把自动装配逻辑串起来。下面以一个简单的验证码 starter 为例。
5.1 项目结构
新建一个 Maven 项目,项目名
captcha-spring-boot-starter
,整体结构如下:
captcha-spring-boot-starter
├── pom.xml
└── src
└── main
├── java
│ └── com
│ └── example
│ └── captcha
│ ├── CaptchaProperties.java
│ ├── CaptchaService.java
│ └── CaptchaAutoConfiguration.java
└── resources
└── META-INF
└── spring
└── org.springframework.boot.autoconfigure.AutoConfiguration.imports
5.2 引入必要依赖
pom.xml 中使用 Spring Boot 3.x 作为基础版本:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<version>3.2.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<version>3.2.0</version>
<optional>true</optional>
</dependency>
</dependencies>
spring-boot-autoconfigure
提供自动配置相关注解,
spring-boot-configuration-processor
用于生成配置元数据,这样引用方在 IDE 里编写
application.yml
时能有配置提示。
5.3 定义配置属性类
@ConfigurationProperties(prefix = "example.captcha")
public class CaptchaProperties {
/**
* 验证码长度
*/
private int length = 4;
/**
* 验证码有效期,单位秒
*/
private int expireSeconds = 60;
public int getLength() {
return length;
}
public void setLength(int length) {
this.length = length;
}
public int getExpireSeconds() {
return expireSeconds;
}
public void setExpireSeconds(int expireSeconds) {
this.expireSeconds = expireSeconds;
}
}
这里通过
@ConfigurationProperties(prefix = "example.captcha")
把配置项绑定到属性字段上。引用方在配置文件中写
example.captcha.length=6
,就会自动绑定到这个类的
length
字段。
5.4 定义业务服务类
public class CaptchaService {
private final CaptchaProperties properties;
public CaptchaService(CaptchaProperties properties) {
this.properties = properties;
}
public String generateCode() {
int length = properties.getLength();
StringBuilder sb = new StringBuilder();
Random random = new Random();
for (int i = 0; i < length; i++) {
sb.append(random.nextInt(10));
}
return sb.toString();
}
}
这个服务类不需要加任何 Spring 注解,因为它不是通过组件扫描注册的,而是由自动配置类显式创建的。
5.5 定义自动配置类
@AutoConfiguration
@EnableConfigurationProperties(CaptchaProperties.class)
@ConditionalOnClass(CaptchaService.class)
@ConditionalOnProperty(prefix = "example.captcha", name = "enabled", havingValue = "true", matchIfMissing = true)
public class CaptchaAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public CaptchaService captchaService(CaptchaProperties properties) {
return new CaptchaService(properties);
}
}
关键点解释:
-
@AutoConfiguration是 Spring Boot 2.7 之后的标准写法,标记这是一个自动配置类。 -
@EnableConfigurationProperties(CaptchaProperties.class)将配置属性类注册为 Bean,并开启属性绑定。 -
@ConditionalOnClass(CaptchaService.class)确保 classpath 下有这个类才加载。如果在独立模块中,这个条件一定成立,这里只是为了演示写法。 -
@ConditionalOnProperty让使用者可以通过example.captcha.enabled=false来关闭这个自动配置。 -
@ConditionalOnMissingBean允许使用者自定义覆盖默认的CaptchaServiceBean。
5.6 注册自动配置类
在
resources/META-INF/spring/
目录下创建
org.springframework.boot.autoconfigure.AutoConfiguration.imports
文件,内容:
com.example.captcha.CaptchaAutoConfiguration
5.7 在业务项目中使用
创建一个普通的 Spring Boot 项目,引入这个 starter:
<dependency>
<groupId>com.example</groupId>
<artifactId>captcha-spring-boot-starter</artifactId>
<version>1.0.0</version>
</dependency>
然后在任意一个
@Service
中注入
CaptchaService
:
@Service
public class DemoService {
private final CaptchaService captchaService;
public DemoService(CaptchaService captchaService) {
this.captchaService = captchaService;
}
public String getCaptcha() {
return captchaService.generateCode();
}
}
运行项目后,
CaptchaService
会被自动注入,无需手动写任何
@Bean
方法。如果想要关闭自动装配,在
application.yml
中配置:
example:
captcha:
enabled: false
再次启动时,
CaptchaAutoConfiguration
就会被条件注解拦截,
CaptchaService
也不会注册,此时注入就会报错。这个开关机制在写公共组件时非常实用。
6. 自动装配的常见误区
6.1 误区一:自动装配等于全自动扫描
自动装配只在候选配置类的范围内生效,不负责扫描你项目里所有类。
@SpringBootApplication
中的
@ComponentScan
默认只扫描启动类所在包及子包,自动装配加载的是
AutoConfiguration.imports
里列出的配置类。两者是独立的路径,不要混为一谈。
6.2 误区二:引入了 starter 就一定生效
starter 引入后,自动配置类只是进入了候选名单,最终是否生效要看:
- classpath 依赖是否满足条件。
- 容器中是否已有用户自定义Bean。
-
application.yml中的开关配置是否允许。
例如引入了
spring-boot-starter-data-redis
,但如果项目里没有 Redis 连接相关的客户端实现,
RedisAutoConfiguration
也会跳过。
6.3 误区三:
@ConditionalOnMissingBean
一定能兜底
@ConditionalOnMissingBean
的判断时机是在自动配置类生效时。如果你在别的配置类中,通过
@Bean
手动定义了同类型 Bean,且这个配置类先于自动配置类执行,那自动配置就会让位。如果顺序反过来,可能出现两个候选 Bean,启动时就会冲突。
6.4 误区四:配置类和普通
@Configuration
完全等价
自动配置类的加载时机和处理逻辑与普通
@Configuration
不同。自动配置类由
AutoConfigurationImportSelector
统一批量导入,并且在导入后会经过
ConfigurationClassFilter
过滤。普通
@Configuration
类则走常规的配置类解析流程。在自定义自动配置类时,不能假设它和普通配置类的执行时机完全相同。
7. 自动装配的加载链路速查
把整个加载链路完整梳理一遍:
-
启动类标注
@SpringBootApplication,内部引入@EnableAutoConfiguration。 -
@EnableAutoConfiguration通过@Import(AutoConfigurationImportSelector.class)开启自动配置导入。 -
AutoConfigurationImportSelector调用getAutoConfigurationEntry,从AutoConfiguration.imports中读取候选配置类。 -
通过
removeDuplicates去重,移除exclude指定的类。 -
通过
ConfigurationClassFilter对每个候选类上的@ConditionalOnXxx注解进行条件判断。 -
通过
fireAutoConfigurationImportEvents发布导入事件,允许监听器对导入结果做进一步处理。 -
通过
AutoConfigurationImportListener或AutoConfigurationImportEvent可以自定义干预自动配置类的导入逻辑。
如果需要在项目中排查自动装配相关内容,重点关注
spring-boot-autoconfigure
包中的这几个类:
-
AutoConfigurationImportSelector:核心导入器。 -
AutoConfigurationImportEvent:导入事件。 -
AutoConfigurationImportListener:导入监听器。 -
AutoConfigurationSorter:排序器,处理自动配置类之间的先后关系。
8. 版本差异与升级建议
自动装配在 Spring Boot 3.x 中做了一些重要调整,主要集中在这几处:
-
spring.factories方式被移除,统一使用AutoConfiguration.imports。 -
@AutoConfigureBefore、@AutoConfigureAfter调整为@AutoConfiguration(before = ...)和@AutoConfiguration(after = ...)。 -
@AutoConfigurationPackage注册的包名与启动类所在包保持一致,这个逻辑没有变化。 - Jakarta EE 包名替换了 javax 命名空间,但在自动装配层面影响不大。
如果你正在把项目从 Spring Boot 2.x 升级到 3.x,建议按这个顺序处理:
-
全局搜索
spring.factories,把所有自动配置类迁移到AutoConfiguration.imports。 -
全局搜索
@AutoConfigureBefore和@AutoConfigureAfter,改成新注解写法。 -
用
debug: true启动项目,查看自动配置报告,确认所有关键自动配置类处于matched状态。 - 重点核对数据源、Redis、MyBatis 等核心自动配置是否正常加载。
9. 实操心得
自动装配这块内容,光看源码很难形成完整画面。我建议在本地创建两个项目:一个是最小化的 Spring Boot 3.x 工程,另一个是自定义 starter。先在第一个项目里通过
debug: true
观察自动配置报告,找一个简单的自动配置类(比如
RedisAutoConfiguration
)看它的条件注解和属性绑定;再动手把自定义 starter 跑通,把
AutoConfiguration.imports
文件、配置属性类、自动配置类的完整链路亲手验证一遍。
实际走完这两步之后,再回来看面试题就会顺很多。比如面试官问"Spring Boot 是如何实现自动装配的",你不需要背概念,直接按照"
@EnableAutoConfiguration
引入
AutoConfigurationImportSelector
,从
AutoConfiguration.imports
读取候选类,通过条件注解过滤,最终注册为 Bean"这条链路讲,再补充一个自定义 starter 的实例,基本就能把问题答透。
最后说一个小技巧:排查自动配置不生效时,先开
debug: true
看条件评估报告,再检查依赖,最后确认用户自定义 Bean 是否干扰。按这个顺序来,九成问题都能在五分钟内定位。
450




被折叠的 条评论
为什么被折叠?



