一张图看懂 Spring Boot:从 main() 到 Tomcat 就绪,自动配置是怎么悄悄发生的(2026)
一张图看懂 Spring Boot:从 main() 到 Tomcat 就绪,自动配置是怎么悄悄发生的(2026)
📖 摘要:为什么只写一个
@SpringBootApplication加一个main(),Web 项目就能直接跑起来、还自带 Tomcat?本文用一张"启动流水线"图,把 Spring Boot 自动配置的核心机制讲透:复合注解@SpringBootApplication拆成哪三块、AutoConfigurationImportSelector如何从 classpath 读候选、@Conditional家族如何按需装配,以及"为什么你自己写的 Bean 永远优先"。读完你不仅能讲清面试八股,还能动手写自己的 Starter。🏷️ 关键词:Spring Boot,自动配置,@Conditional,Starter,IoC 容器
目录
一、为什么需要自动配置
在 Spring Boot 之前,搭一个 Web 项目意味着:写 web.xml、配 DispatcherServlet、配 DataSource、配事务管理器、配视图解析器……一大堆 XML 或 @Configuration 样板。这些配置 90% 的情况下都是"标准答案",跟业务无关,却每次都要手写。
Spring Boot 的核心哲学是约定大于配置(convention over configuration):它把"场景化标准答案"提前写成自动配置类,你只要引入对应的 starter 依赖,框架就根据 classpath 上"有什么类、缺什么 Bean、配了什么参数"自动把组件装配好。你只关心业务代码。
💡 一句话:自动配置 = 一批写好的
@Configuration类 + 一套条件判断规则,启动时按条件挑出要生效的那几个。
二、@SpringBootApplication 到底打包了什么
2.1 三个子注解各管什么事
@SpringBootApplication 是一个复合注解,本质就是下面三个:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@SpringBootConfiguration // = @Configuration,标记启动类本身是配置类
@EnableAutoConfiguration // 开启自动配置(本文主角)
@ComponentScan(...) // 扫描启动类同包及子包下的业务组件
public @interface SpringBootApplication {}
三者的分工非常清晰:
| 子注解 | 负责 | 不写会怎样 |
|---|---|---|
@SpringBootConfiguration | 让启动类能声明 @Bean | 无法在启动类里定义 Bean |
@ComponentScan | 扫描你写的 @Controller/@Service/@Repository | 你自己的组件不会被注册进容器 |
@EnableAutoConfiguration | 触发框架自带的自动配置 | 没有 Tomcat、没有 DataSource 自动装配,项目跑不起来 |
2.2 自动配置的核心入口
真正干活的,是 @EnableAutoConfiguration 里这一行:
@Import(AutoConfigurationImportSelector.class) // 自动配置选择器
public @interface EnableAutoConfiguration {}
AutoConfigurationImportSelector 实现了 DeferredImportSelector,它在容器初始化到"处理完用户自定义配置"这一步时,才把自动配置类批量导入——这正是"用户 Bean 优先"的关键时间点,后面会展开。
三、自动配置加载全流程
3.1 候选清单从哪来
AutoConfigurationImportSelector 启动后会扫描 classpath 上所有 jar 包里的这个文件:
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
⚠️ 版本差异:Spring Boot 2.x 用的是
META-INF/spring.factories里的EnableAutoConfiguration键;Boot 3.0 起彻底移除spring.factories,只用AutoConfiguration.imports。迁移老项目时这是高频踩坑点。
spring-boot-autoconfigure 这个 jar 里就躺着约 150 个自动配置类,例如:
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration
选择器读出全部类名 → 去重 → 剔除 @EnableAutoConfiguration(exclude=...) 排除项 → 用 AutoConfigurationImportFilter 做一次"类路径快速校验" → 按 @AutoConfigureBefore/After/Order 排序 → 交给容器。
3.2 @Conditional 条件装配
候选不是无脑全生效,每个类都被条件注解"把门"。你日常会遇到的几个:
| 条件注解 | 触发条件 | 典型用途 |
|---|---|---|
@ConditionalOnClass | classpath 上有某类 | “只有引入 Lettuce 才配 Redis” |
@ConditionalOnMissingClass | 该类缺失 | 在两个竞争库之间二选一 |
@ConditionalOnBean | 容器已有某类型 Bean | 给已有 Bean 做装饰 |
@ConditionalOnMissingBean | 容器没有该 Bean | 让你自定义 Bean 覆盖默认 |
@ConditionalOnProperty | 配置项匹配 | 功能开关 |
@ConditionalOnWebApplication | 是 Web 环境 | 仅 Web 上下文生效 |
核心机制在 @ConditionalOnClass:它用 ASM 字节码元数据读取类名,而不是真的去 Class.forName 加载类。所以即便某个类不在 classpath 上,也不会抛 NoClassDefFoundError——这正是自动配置能"安全引用不存在的类"的原因。
3.3 排序与"你的 Bean 优先"
自动配置类之间也有依赖,比如 JPA 自动配置要求 DataSource 已存在,这是靠 @AutoConfigureAfter(DataSourceAutoConfiguration.class) 表达的,而不是靠 Bean 依赖。
最关键的一条铁律:你的配置先于自动配置执行,所以当你自己定义了某 Bean,自动配置里的同名 @Bean 因 @ConditionalOnMissingBean 直接失效——你永远赢。
// 你写了这个,RedisAutoConfiguration 里的默认 StringRedisTemplate 就不会再建
@Bean
public StringRedisTemplate myRedisTemplate(RedisConnectionFactory f) {
return new StringRedisTemplate(f);
}
想看"哪些生效、哪些没生效、为什么",启动时加 --debug(或配置 debug=true),控制台会打印一份条件评估报告,几乎能秒答所有"这个 Bean 怎么在这 / 怎么不在"的疑问。
四、动手写一个最小 Starter
4.1 自动配置类
假设我们要做一个"限流"组件,仅当用户引入 app.rate-limit.enabled=true 且 classpath 有 StringRedisTemplate 时生效:
@AutoConfiguration(after = RedisAutoConfiguration.class)
@ConditionalOnClass(StringRedisTemplate.class)
@ConditionalOnProperty(prefix = "app.rate-limit", name = "enabled", havingValue = "true")
@EnableConfigurationProperties(RateLimitProperties.class)
public class RedisRateLimiterAutoConfiguration {
@Bean
@ConditionalOnMissingBean // 用户没自定义才用默认的
public RateLimiter rateLimiter(StringRedisTemplate redis, RateLimitProperties props) {
return new RedisSlidingWindowRateLimiter(redis, props.getLimit(), props.getWindow());
}
}
💡
@AutoConfiguration从 Boot 2.7 起推荐使用,它隐含proxyBeanMethods=false并携带排序信息;Boot 3 里它是自动配置类的标准注解。
4.2 注册与属性绑定
把类名写进 AutoConfiguration.imports(每行一个全限定名,无 key、无反斜杠):
com.example.autoconfigure.RedisRateLimiterAutoConfiguration
属性绑定类用 @ConfigurationProperties 绑定 application.yml:
@ConfigurationProperties(prefix = "app.rate-limit")
public class RateLimitProperties {
private int limit = 100; // 默认阈值
private int window = 60; // 默认窗口(秒)
// getter / setter ...
}
一个标准 Starter 通常由两个模块组成:一个 autoconfigure 模块(带条件与逻辑),一个极薄的 starter 模块(只负责带依赖)。这就是 Spring 官方所有 spring-boot-starter-* 的同款结构。
五、3 个高频踩坑
- 自动配置"不生效":八成是 starter 依赖没引对,或你的包不在启动类同包/子包下导致
@ComponentScan漏扫。先--debug看条件报告,别瞎猜。 - 自定义 Bean 被忽略:确认你是"后于自动配置"定义的。若用了
@Component而非@Bean自己 new,注意循环依赖与代理问题;覆盖默认配置时优先用@Bean+@ConditionalOnMissingBean思路。 - Boot 2.x 升 3.x 配置失效:老
spring.factories的EnableAutoConfiguration键已被删,必须改成AutoConfiguration.imports,否则自动配置类一个都不会加载。
六、总结
把整条流水线串起来就是一句人话:@SpringBootApplication 打开三扇门 → AutoConfigurationImportSelector 从 classpath 收集约 150 个候选配置 → @Conditional 家族按"类在不在、Bean 有没有、参数配没配"逐条筛 → 排序后注册进容器,且永远让你的 Bean 优先。
所谓"魔法",底层全是 Spring 的原生能力:配置类、包扫描、@Import、条件装配。理解了这套,面试官再问自动配置原理,你直接把这张图甩过去就行。
觉得有用就点个赞/收藏,评论区聊聊你写 Starter 时踩过的坑。
更多推荐



所有评论(0)