Spring Boot 自动配置原理:从 @SpringBootApplication 到 AutoConfiguration.
"引入 spring-boot-starter-web 就有内嵌 Tomcat 和 DispatcherServlet",这件事看起来像魔法。拆开看,它由三层机制叠加而成:@SpringBootApplication 的复合注解、ImportSelector 的批量注册、@Conditional 的条件装配。本文沿着 Spring Boot 3.x 的源码路径,把这套机制完整还原,并给出自定义 starter 的写法。适合想理解 Boot 内部机制、或需要编写公司内部 starter 的后端开发阅读。
一、@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 {
@AliasFor(annotation = EnableAutoConfiguration.class)
Class<?>[] exclude() default {};
// ...
}
三个核心注解各司其职:
@SpringBootConfiguration:本质是@Configuration,标记这是配置类。@ComponentScan:扫描启动类所在包及子包。默认排除所有自动配置类(AutoConfigurationExcludeFilter),避免和@Import重复注册。@EnableAutoConfiguration:自动配置的入口。
二、@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 {};
}
@AutoConfigurationPackage 通过 @Import(AutoConfigurationPackages.Registrar.class) 把启动类所在包注册为"自动配置包",MyBatis、JPA 的实体扫描都依赖它。
真正的加载逻辑在 AutoConfigurationImportSelector#selectImports:
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
AutoConfigurationEntry autoConfigurationEntry =
getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
getAutoConfigurationEntry 做了四件事:
- 从注解元数据读取
exclude、excludeName,合并到排除集合。 getCandidateConfigurations通过ImportCandidates.load(AutoConfiguration.class, classLoader)读取候选类名。- 用
AutoConfigurationImportFilter做初步过滤,实际是OnClassCondition、OnBeanCondition、OnWebApplicationCondition三个过滤器。 - 触发
AutoConfigurationImportEvent,并做去重。
2.1 候选类从哪里来
Spring Boot 2.7 之前读的是 META-INF/spring.factories:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.autoconfig.FooAutoConfiguration,\
com.example.autoconfig.BarAutoConfiguration
Spring Boot 2.7 引入新机制,3.x 完全改为 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports:
com.example.autoconfig.FooAutoConfiguration
com.example.autoconfig.BarAutoConfiguration
每行一个全限定类名,不再有 key=value 结构,也不再支持 @AutoConfiguration 之外的组织方式。Spring Boot 3.x 仍会读 spring.factories 中其他 key(如 EnvironmentPostProcessor),但 EnableAutoConfiguration 这个 key 已被移除支持。
2.2 排序与去重
自动配置类之间用 @AutoConfigureBefore、@AutoConfigureAfter、@AutoConfigureOrder 声明顺序。例如 DataSourceAutoConfiguration 必须在 MybatisAutoConfiguration 之前完成,否则 SqlSessionFactory 拿不到数据源。底层由 AutoConfigurationSorter 做拓扑排序。
去重通过 Set 和 LinkedHashSet 保证,同一个配置类被多个 starter 引入时只注册一次。
三、条件装配:@Conditional 家族
自动配置类通常长这样:
@AutoConfiguration
@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
@ConditionalOnMissingBean(type = "io.r2dbc.spi.ConnectionFactory")
@EnableConfigurationProperties(DataSourceProperties.class)
@Import({ DataSourcePoolMetadataProvidersConfiguration.class,
DataSourceCheckpointRestoreConfiguration.class })
public class DataSourceAutoConfiguration {
@Configuration(proxyBeanMethods = false)
@Conditional(PooledDataSourceCondition.class)
@ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
@Import({ DataSourceConfiguration.Hikari.class, DataSourceConfiguration.Tomcat.class })
protected static class PooledDataSourceConfiguration {
}
}
常见条件注解:
| 注解 | 判断依据 | 典型用途 |
| --- | --- | --- |
| @ConditionalOnClass | 类路径存在指定类 | starter 检测依赖是否引入 |
| @ConditionalOnMissingBean | 容器中没有指定类型/名称的 Bean | 允许用户覆盖默认实现 |
| @ConditionalOnBean | 容器中存在指定 Bean | 依赖其他配置的产物 |
| @ConditionalOnProperty | 配置项匹配 | 功能开关 |
| @ConditionalOnWebApplication | 应用类型是 SERVLET/REACTIVE | 区分 Web 与普通应用 |
| @ConditionalOnResource | 资源文件存在 | 读取可选配置 |
| @ConditionalOnExpression | SpEL 表达式为真 | 复杂组合条件 |
@ConditionalOnClass 的实现有个细节:不能直接写 DataSource.class 引用,否则类不存在时加载注解本身就会抛 NoClassDefFoundError。Spring Boot 用 OnClassCondition 配合 ASM 读取注解元数据,避免真正加载类。
3.1 条件评估的时机
条件在 ConfigurationClassPostProcessor#processConfigBeanDefinitions 阶段评估,此时只注册了 BeanDefinition,Bean 还没实例化。因此 @ConditionalOnBean 只能看到已经注册的 Bean 定义,与声明顺序强相关。在普通 @Configuration 类里用 @ConditionalOnBean 容易因为顺序问题失效,官方建议只在自动配置类中使用。
四、属性绑定
@EnableConfigurationProperties 把 @ConfigurationProperties 类注册为 Bean,配合 @ConfigurationPropertiesScan 可批量扫描。
@ConfigurationProperties(prefix = "app.mail")
public record MailProperties(String host, int port, String username, String password) {
}
Spring Boot 3.x 支持构造器绑定(含 record),要求类只有一个有参构造器,或用 @ConstructorBinding 显式指定(3.0 起单构造器时可省略)。松散绑定规则把 app.mail.username、app.mail.user-name、APP_MAIL_USERNAME 都映射到同一属性。
绑定失败时启动直接报错,这是 Boot 的默认行为,比运行时 NullPointerException 好排查得多。
五、自定义配置的优先级
自动配置的"退让"机制依赖一个前提:用户配置先于自动配置注册。@ConditionalOnMissingBean 只有在用户 Bean 已经注册时才会跳过默认实现。
Spring Boot 保证这一点的做法是:自动配置类通过 @Import 导入,而 @ComponentScan 扫描的用户配置类在 ConfigurationClassParser 的解析顺序中更靠前。ConfigurationClassParser#processConfigurationClass 会先处理 @ComponentScan,再处理 @Import,所以用户 Bean 定义先进入容器。
配置属性的覆盖顺序则是另一套规则,优先级从高到低:
- 命令行参数
--server.port=9090 SPRING_APPLICATION_JSON- Servlet 的
ServletConfig与ServletContext参数 - JNDI
- Java 系统属性
-Dserver.port=9090 - 操作系统环境变量
SERVER_PORT application-{profile}.properties/yml(jar 外部)application-{profile}.properties/yml(jar 内部)application.properties/yml(jar 外部)application.properties/yml(jar 内部)@PropertySource- 默认值
SpringApplication.setDefaultProperties
环境变量名到属性名的映射规则:大写、点换下划线、短横线换下划线,例如 spring.datasource.url 对应 SPRING_DATASOURCE_URL。这一条在容器化部署时特别重要,K8s 的 ConfigMap 注入通常走这条路。
六、自己写一个 starter
6.1 目录结构
mail-spring-boot-starter/
├── pom.xml
└── src/main/
├── java/com/example/mail/
│ ├── MailProperties.java
│ ├── MailClient.java
│ └── MailAutoConfiguration.java
└── resources/META-INF/spring/
└── org.springframework.boot.autoconfigure.AutoConfiguration.imports
6.2 自动配置类
@AutoConfiguration
@ConditionalOnClass(MailClient.class)
@EnableConfigurationProperties(MailProperties.class)
public class MailAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public MailClient mailClient(MailProperties properties) {
return new MailClient(properties.host(), properties.port());
}
}
@AutoConfiguration 等价于 @Configuration(proxyBeanMethods = false) 并额外支持 after、before 属性,是 Boot 3 推荐的写法。
6.3 注册文件
src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports:
com.example.mail.MailAutoConfiguration
不需要 spring.factories。如果项目同时要兼容 Boot 2.x,两个文件都放,spring.factories 中的 key 仍是 org.springframework.boot.autoconfigure.EnableAutoConfiguration。
6.4 用户覆盖
业务方想自定义 MailClient 时,只需自己声明一个同类型 Bean,@ConditionalOnMissingBean 会让默认实现退让。这就是"约定优于配置"的落点。
七、排查自动配置问题
Boot 提供了三种定位手段,按成本从低到高排列。
第一种是启动参数 debug=true。它在控制台输出 ConditionEvaluationReport,分为 Positive matches(生效)与 Negative matches(未生效)两段,每条都附带原因:
DataSourceAutoConfiguration matched:
- @ConditionalOnClass found required classes 'javax.sql.DataSource',
'org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType' (OnClassCondition)
DataSourceAutoConfiguration#dataSource matched:
- @ConditionalOnMissingBean (types: javax.sql.DataSource; SearchStrategy: all)
did not find any beans (OnBeanCondition)
DataSourceAutoConfiguration.JdbcTemplateConfiguration#jdbcTemplate did not match:
- @ConditionalOnSingleCandidate (types: javax.sql.DataSource; SearchStrategy: all)
did not find a bean (OnBeanCondition)
第二种是直接注入 ConditionEvaluationReport 这个 Bean,在运行期以编程方式读取评估结果,适合暴露到健康检查端点。
第三种是 spring.autoconfigure.exclude。确认某个自动配置确实不需要时,用配置显式排除,比在启动类上写 exclude 更利于多环境差异化管理:
spring:
autoconfigure:
exclude:
- org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
- org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration
八、常见坑 / 最佳实践
- 自动配置类不要放在启动类所在包及其子包下。否则会被
@ComponentScan重复扫描,@ConditionalOnMissingBean的判断时机错乱。放在独立的 starter 包中,靠AutoConfiguration.imports加载。 @ConditionalOnBean写在普通配置类里。条件评估依赖注册顺序,普通配置类的顺序不受控,容易时灵时不灵。自动配置类有拓扑排序保障,才适合用它。- 升级到 Boot 3 后
spring.factories里的自动配置不生效。3.x 不再读取EnableAutoConfigurationkey,必须迁移到AutoConfiguration.imports。这是升级时最容易踩的坑。 - 自动配置类加
@ComponentScan。会导致扫描范围失控,把使用方的 Bean 也扫进来。自动配置类只应通过@Bean和@Import注册自己需要的组件。 - 用
@ConditionalOnProperty却忘了matchIfMissing。默认值为false,未配置时条件不成立,功能静默关闭。需要默认开启时显式写matchIfMissing = true。 - 排查时打开
debug=true。application.properties加debug=true会打印ConditionEvaluationReport,逐条列出哪些自动配置命中、哪些未命中及原因,比猜快得多。
九、小结
@SpringBootApplication=@SpringBootConfiguration+@EnableAutoConfiguration+@ComponentScan。@EnableAutoConfiguration通过@Import(AutoConfigurationImportSelector.class)批量导入配置类。- Spring Boot 3.x 从
META-INF/spring/...AutoConfiguration.imports读取候选类,不再使用spring.factories。 @ConditionalOnClass、@ConditionalOnMissingBean等条件注解决定配置类是否生效,是"约定优于配置"的实现手段。- 自定义 starter 只需三步:写自动配置类、声明条件、放入 imports 文件。
- 点赞
- 收藏
- 关注作者
评论(0)