SpringBoot 自动装配原理详解
自动装配是 Spring Boot 的灵魂:引入一个 Starter、加几行配置就能获得一整套开箱即用的 Bean。其底层是 @Import 导入机制、SPI 候选清单与 @Conditional 条件评估三者的协作。本文从组合注解拆到源码流程,最后手写一个完整 Starter 收尾。
它解决什么问题
在没有 Spring Boot 的年代,给一个 Spring 项目接入 Web MVC,需要手写 ViewResolver、Jackson 视图等一串 JavaConfig,再配一份 spring-servlet.xml 声明组件扫描与注解驱动;接入 Redis、JPA、MQ 同样要重复这套「查文档、抄配置、调版本」的仪式。配置本身没有业务价值,却消耗大量时间,还极易因版本错配而启动失败。
自动装配要消除的正是这种重复:框架按「约定」准备好各个技术栈的默认配置类,按条件筛选出当前类路径与配置下真正需要的那部分,自动注册进容器;用户一旦定义了同类 Bean,默认实现主动退让。一句话概括——约定优于配置,条件决定装配,用户定义优先。
核心原理
一切从启动类上的 @SpringBootApplication 说起,它是三个注解的组合体:
// 本质是 @Configuration,标记启动类即配置类 |
@ComponentScan 只负责扫「自己项目」的包,第三方 jar 里的配置类扫不到,这就要靠 @EnableAutoConfiguration 出场:
// 把启动类所在包登记为自动配置的基准包 |
核心是 @Import 导入的 AutoConfigurationImportSelector。@Import 支持三种导入方式:普通配置类、ImportSelector(接口方法返回一批要注册的全限定类名)、ImportBeanDefinitionRegistrar(直接编程式注册 BeanDefinition)。AutoConfigurationImportSelector 实现的是 DeferredImportSelector,意味着它会推迟到所有常规 @Configuration 处理完之后才执行——这个「最后登场」的顺序是「用户定义优先」能够成立的前提。
候选配置类从哪里来?这经历了注册机制的演进:
- Spring Boot 2.6 及更早:候选清单写在各 jar 包的
META-INF/spring.factories中,以EnableAutoConfiguration为 key,由SpringFactoriesLoader合并加载类路径下所有同名文件; - Spring Boot 2.7:引入新文件
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports,每行一个全限定类名,与旧机制并行兼容; - Spring Boot 3.0:彻底移除通过
spring.factories的EnableAutoConfigurationkey 注册自动配置的支持(该文件的其他用途不受影响),.imports成为唯一入口,自动配置类推荐改用@AutoConfiguration标注。
深入机制
筛选流程:从全量候选到按需生效
AutoConfigurationImportSelector#selectImports 的核心动作在 getAutoConfigurationEntry 中,流程固定为六步:
- 检查开关
spring.boot.enableautoconfiguration,关闭则直接返回空; - 读取注解上的
exclude/excludeName,得到用户显式排除的名单; - 调用
getCandidateConfigurations读取.imports(旧版为spring.factories)拿到全部候选类名,以 Redis 为例会拿到RedisAutoConfiguration等; - 去重、剔除排除项;
- 通过
AutoConfigurationImportFilter(如OnClassCondition、OnWebApplicationCondition)做批量条件预筛——类路径下没有JedisConnection或RedisOperations,Redis 的自动配置在这一步就被刷掉; - 触发
AutoConfigurationImportEvent,把最终名单交给配置类解析器。
通过预筛的配置类进入常规解析后,还要过方法级的 @Conditional 评估,每个 @Bean 方法上的条件都满足才真正注册。所以「几百个候选」最终生效的往往只有几十个,这正是自动装配不是「全量装配」的原因。
@Conditional 条件装配族
条件体系全部建立在 Spring Framework 的 @Conditional 之上,Boot 提供了一组面向常见场景的派生注解:
| 注解 | 生效条件 | 典型用途 |
|---|---|---|
@ConditionalOnClass / @ConditionalOnMissingClass |
类路径存在/不存在指定类 | 判断用户是否引入了某依赖 |
@ConditionalOnBean / @ConditionalOnMissingBean |
容器中已有/没有指定 Bean | 实现默认实现的「退让」 |
@ConditionalOnProperty |
配置属性匹配指定值 | 功能开关,如 spring.aop.auto |
@ConditionalOnWebApplication / @ConditionalOnNotWebApplication |
当前是否 Web 环境 | 区分 Servlet/Reactive 装配 |
@ConditionalOnSingleCandidate |
指定类型只有一个候选或有首选 | 注入 DataSource 等单例依赖 |
@ConditionalOnExpression |
SpEL 表达式为真 | 复杂组合判断 |
其中 @ConditionalOnMissingBean 是理解 Boot 设计哲学的钥匙:官方自动配置里的 RedisTemplate、ObjectMapper 等默认 Bean 几乎都挂着它——只要你自己声明了同类型 Bean,默认实现就不再注册。这就是为什么「自己写一个就生效」不需要任何额外开关。
排查手段
条件判断失败是自动装配最常见的问题面。三个实用工具:启动加 debug=true 打印条件评估报告,按「Positive matches / Negative matches」分组列出每个配置类的匹配结论;Actuator 环境下访问 /actuator/conditions 端点;需要编程式断言时用 ConditionEvaluationReport 直接取报告对象。
工程实践:手写一个 Starter
以一个输出格式化组件为例,完整走一遍自定义 Starter 的标准结构(面向 Spring Boot 2.7+ / 3.x)。
第一步,定义属性类,承载可配置项并指定前缀:
|
第二步,编写核心服务与自动配置类,用条件注解圈定生效边界:
public class FormatTemplate { |
第三步,注册候选清单。在 autoconfigure 模块的 resources 下新建 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports,每行一个类名:
com.example.format.FormatAutoConfiguration |
若需兼容 Spring Boot 2.6 及更早版本,则改为在 META-INF/spring.factories 中写 org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.example.format.FormatAutoConfiguration。
第四步,组织模块与验证。惯例是把 Starter 做成聚合依赖的空壳(pom 里引入 autoconfigure 模块与必要运行库),自动配置代码独立成模块;业务方引入 Starter 后,直接用 ApplicationContextRunner 测试三类组合——无配置默认生效、设置 demo.format.prefix 后属性被绑定、用户自定义 FormatTemplate 后默认 Bean 退让。三类场景都通过,才算一个合格的 Starter。
对比与权衡
| 维度 | 显式 @Import/JavaConfig |
自动装配 |
|---|---|---|
| 接入成本 | 每个组件手写配置 | 引依赖即用 |
| 可控性 | 完全显式,所见即所得 | 决策部分推迟到运行时条件评估 |
| 升级风险 | 配置随代码评审演进 | 类路径或属性变化可能静默切换实现 |
| 排查成本 | 低 | 需要读条件评估报告 |
实践建议:自动装配只负责提供「合理默认值」,Starter 不应替业务做不可逆决定;覆盖默认 Bean 优先用属性开关,其次才是自定义 Bean 顶掉——后者依赖框架内部实现细节,版本升级时更容易失效。升级 Boot 大版本前后,对比两次的条件报告比「应用能启动」更能暴露问题。
面试视角
自动装配的整体流程是什么? @SpringBootApplication 中的 @EnableAutoConfiguration 通过 @Import 引入 AutoConfigurationImportSelector;容器处理配置类时,它以 DeferredImportSelector 身份最后执行,读取 .imports(旧版 spring.factories)候选清单,经过去重、排除、@Conditional 条件评估后,把存活的自动配置类注册进容器。
spring.factories 和 AutoConfiguration.imports 有什么区别? 前者是 Properties 格式、以 EnableAutoConfiguration 为 key 的多用途 SPI 文件,2.6 及更早版本使用;后者是 2.7 引入的专用清单文件,一行一个类名,3.0 起成为自动配置的唯一注册方式,加载性能更好且职责单一。
为什么自动配置能实现「用户优先」? 两个机制叠加:AutoConfigurationImportSelector 作为 DeferredImportSelector 在用户配置之后处理,此时用户 Bean 的定义已存在;自动配置里的默认 Bean 又普遍标注 @ConditionalOnMissingBean,检测到已有同类型定义便放弃注册。
怎么排查某个自动配置没生效? 首选启动参数加 debug=true,在条件评估报告的 Negative matches 里找该类,报告会写明是哪个条件不满足(缺类、缺 Bean 还是属性不匹配);也可检查 exclude 配置与依赖树,确认 jar 真的在类路径上。
手写 Starter 的关键步骤? 属性类(@ConfigurationProperties)+ 自动配置类(@AutoConfiguration + 条件注解 + @ConditionalOnMissingBean 默认 Bean)+ .imports 注册文件三步缺一不可,最后用 ApplicationContextRunner 覆盖「默认、配属性、用户覆盖」三种组合做测试。
小结
@SpringBootApplication=@SpringBootConfiguration+@ComponentScan+@EnableAutoConfiguration,前两者管「自己人」,后者管「第三方」;- 自动装配 =
@Import导入候选清单 +@Conditional条件筛选,DeferredImportSelector的延后执行顺序保证了用户定义优先; - 注册机制已从
spring.factories演进到AutoConfiguration.imports,面向 3.x 的新代码只写后者; - 排查思路围绕条件评估报告展开,自定义 Starter 的核心纪律是「只给默认值,可覆盖,可关闭」。

