一张图看懂 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 条件装配

候选不是无脑全生效,每个类都被条件注解"把门"。你日常会遇到的几个:

条件注解触发条件典型用途
@ConditionalOnClassclasspath 上有某类“只有引入 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 个高频踩坑

  1. 自动配置"不生效":八成是 starter 依赖没引对,或你的包不在启动类同包/子包下导致 @ComponentScan 漏扫。先 --debug 看条件报告,别瞎猜。
  2. 自定义 Bean 被忽略:确认你是"后于自动配置"定义的。若用了 @Component 而非 @Bean 自己 new,注意循环依赖与代理问题;覆盖默认配置时优先用 @Bean + @ConditionalOnMissingBean 思路。
  3. Boot 2.x 升 3.x 配置失效:老 spring.factories 的 EnableAutoConfiguration 键已被删,必须改成 AutoConfiguration.imports,否则自动配置类一个都不会加载。

六、总结

把整条流水线串起来就是一句人话:@SpringBootApplication 打开三扇门 → AutoConfigurationImportSelector 从 classpath 收集约 150 个候选配置 → @Conditional 家族按"类在不在、Bean 有没有、参数配没配"逐条筛 → 排序后注册进容器,且永远让你的 Bean 优先。

所谓"魔法",底层全是 Spring 的原生能力:配置类、包扫描、@Import、条件装配。理解了这套,面试官再问自动配置原理,你直接把这张图甩过去就行。

觉得有用就点个赞/收藏,评论区聊聊你写 Starter 时踩过的坑。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐