Spring Boot 自动配置原理:从源码到实战,彻底搞懂 @SpringBootApplication
摘要 :为什么一个
@SpringBootApplication注解就能让整个项目跑起来?本文深入@EnableAutoConfiguration底层机制,梳理@Conditional家族的作用方式,并通过手写自定义 Starter 掌握自动配置的核心用法。适合有基础 Spring 经验、想了解 Spring Boot "魔法"到底怎么来的开发者。阅读后你将理解 Spring Boot 自动配置的完整链路,并能独立编写自己的自动配置类。
目录
- 引言
- 自动配置解决了什么问题
- [@SpringBootApplication 解剖](#@SpringBootApplication 解剖)
- 自动配置的核心流程
- [@Conditional 家族详解](#@Conditional 家族详解)
- [手写一个自定义 Starter](#手写一个自定义 Starter)
- 常见问题与最佳实践
- 小结
- 下一步
1. 引言
如果你刚接触 Spring Boot,最让你震撼的可能就是它的"零配置"能力------建好项目、加个注解、敲一行 java -jar,服务就跑起来了。
但用了一段时间之后,你会开始好奇:
- Spring Boot 是怎么知道该创建哪些 Bean 的?
- 为什么加了
spring-boot-starter-web就自动有了 Tomcat 和 Spring MVC? - 我想让自己的库也具备这种"开箱即用"的能力,该怎么做?
这些问题都指向同一个答案:自动配置(Auto Configuration)。
这篇文章的目标是讲清楚两件事:一是 Spring Boot 自动配置的底层原理,二是如何用它来实现自定义的 Starter。整篇文章遵循"先理解原理、再动手实践"的路径。
环境说明:
| 组件 | 版本 |
|---|---|
| JDK | 17 |
| Spring Boot | 3.2.x |
| Maven | 3.8+ |
注意:Spring Boot 2.x 和 3.x 在自动配置的机制上没有本质区别,核心流程一致。但 3.x 全面转向 Jakarta EE 包名(
javax.*→jakarta.*),示例代码均使用 3.x 标准。
2. 自动配置解决了什么问题
2.1 传统 Spring 的问题
在没有 Spring Boot 之前,我们用 Spring Framework 搭建一个 Web 项目,applicationContext.xml 大概长这样:
xml
<!-- 传统 Spring 配置 -->
<beans>
<!-- 数据源 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${db.driver}"/>
<property name="url" value="${db.url}"/>
<property name="username" value="${db.username}"/>
<property name="password" value="${db.password}"/>
</bean>
<!-- EntityManagerFactory -->
<bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="packagesToScan" value="com.example.entity"/>
</bean>
<!-- 事务管理 -->
<bean id="transactionManager"
class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>
<!-- Spring MVC -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/"/>
<property name="suffix" value=".jsp"/>
</bean>
<!-- 各种其他 Bean... -->
</beans>
这套方案的痛点很明显:
- 配置量大:每个第三方组件都需要手动定义对应的 Bean
- 版本耦合:不同版本的组件 API 可能差异很大,配置不一定通用
- 缺配置即报错:少配一个 Bean,应用直接起不来
- 样板重复:新建项目时大部分配置都是复制粘贴
2.2 Spring Boot 的方案
同样的需求,Spring Boot 只需要:
yaml
# application.yml
spring:
datasource:
url: jdbc:mysql://localhost:3306/mydb
username: root
password: ${DB_PASSWORD}
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
hibernate:
ddl-auto: update
show-sql: true
加上两个依赖、一个注解,完事。
这背后靠的就是**约定优于配置(Convention over Configuration)**的思想 + 条件化装配机制。Spring Boot 内置了一套"默认配置清单",当你的classpath中存在某个框架的 Jar 包时,它会自动帮你把对应的 Bean 装配好。除非你需要定制,才自己去覆盖默认值。
2.3 自动配置的工作原理概览
是
否
启动 Spring Boot
扫描 @SpringBootApplication
解析 @EnableAutoConfiguration
通过 SPI 读取 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
加载全部候选自动配置类
逐一判断 @Conditional 条件是否满足
条件满足?
注册该配置类中的 Bean
跳过该配置类
完成自动配置
整个过程的核心链条是:SPI 发现 → 条件筛选 → Bean 注册。下面逐段拆解。
3. @SpringBootApplication 解剖
@SpringBootApplication 本质上是一个组合注解,它本身不做任何事,而是把三个注解合在一起:
java
// Spring Boot 3.2.x 源码简化版
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@SpringBootConfiguration // ①
@EnableAutoConfiguration // ②
@ComponentScan(excludeFilters = { ... }) // ③
public @interface SpringBootApplication {
// ... 属性定义
}
逐个看:
3.1 @SpringBootConfiguration
java
@Configuration
public @interface SpringBootConfiguration {}
它就是标注当前类为一个配置类。实际上用的时候这个注解可以省略,因为 @Component 族注解都隐含了 @Configuration 的效果。它存在更多是为了语义清晰------告诉读者"这个类负责引导 Spring Boot 应用"。
3.2 @ComponentScan
负责按包扫描并注册 Bean。@SpringBootApplication 上的默认行为是扫描当前类所在包及其子包 。这也是为什么 Spring Boot 推荐把主启动类放在根包下的原因。
3.3 @EnableAutoConfiguration(核心)
这才是今天的主角。它的定义如下:
java
@AutoConfigurationPackage // ④
@Import(AutoConfigurationImportSelector.class) // ⑤
public @interface EnableAutoConfiguration {
String attribute();
}
- ④
@AutoConfigurationPackage:将自动配置类所在的包注册为基础包,配合Registrar实现"把主类所在包下的组件注册为 Bean"。简单说,你主类放哪个包,Spring Boot 就去哪个包里找 Component 标注的类。 - ⑤
@Import(AutoConfigurationImportSelector.class):这是关键------导入一个ImportSelector,在容器初始化时动态选择一批配置类注册进容器。所有的自动配置逻辑都从这里开始触发。
4. 自动配置的核心流程
让我们追踪 AutoConfigurationImportSelector 的核心方法调用链。
4.1 入口:selectImports()
java
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
// 懒加载,第一次被调用时才执行
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(
getAutoConfigurationMetadata(), annotationMetadata);
return autoConfigurationEntry.getConfigurations().toArray(new String[0]);
}
这一步做了两件事:
- 获取
AutoConfigurationMetadata(主要是从spring.factories或.imports文件中读到的配置信息) - 筛选出需要生效的配置类,返回它们的全限定类名数组
4.2 筛选:getAutoConfigurationEntry()
java
protected AutoConfigurationEntry getAutoConfigurationEntry(
AutoConfigurationMetadata autoConfigurationMetadata,
AnnotationMetadata annotationMetadata) {
Map<String, Object> attributes = getAttributes(annotationMetadata);
// 1. 获取所有候选配置类
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
// 2. 去重
configurations = removeDuplicates(configurations);
// 3. 根据 @SpringBootApplication 的属性排除某些配置
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
// 4. 过滤:只保留 @Conditional 满足条件的
configurations = filter(configurations);
// 5. 发布事件 & 缓存
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, attributes);
}
这条链路中最重要的步骤是 第 1 步(获取候选列表) 和 第 4 步(条件过滤)。
4.3 步骤 1:获取候选配置类
Spring Boot 2.7 及以前,配置文件写的是 META-INF/spring.factories。从 Spring Boot 2.7+ / 3.0 开始,改用新的 SPI 文件:
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
每行一个自动配置类的全限定类名:
# META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration
org.springframework.boot.autoconfigure.availability.ApplicationAvailabilityAutoConfiguration
org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration
...(一共几百个)
AutoConfigurationImportSelector 会用 SpringFactoriesLoader 来读取这个文件:
java
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
Map<String, Object> attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
getSpringFactoryInterface(metadata),
getSpringFactoriesLoaderClassFinder(),
getSpringFactoriesLoaderClassLoader()
);
return configurations;
}
4.4 步骤 4:条件过滤
这就是自动配置的"智能之处"------几百个候选配置类,但不是全部生效。Spring Boot 引入了 @Conditional 系列注解来做精细化的条件判断。我们接下来展开讲。
4.5 关键结论:自动配置的装配时机
自动配置发生在以下阶段:
refresh() 前置处理
processFactoryMethodExpressions
@EnableAutoConfiguration ImportSelector 回调
selectImports 返回配置类名列表
@Import 注册这些配置类
BeanDefinition 加载
普通 Bean 生命周期
它在 AbstractApplicationContext.refresh() 过程中,早于 用户自己定义的 Bean(除非你用 @DependsOn 强制指定顺序)。这意味着用户可以定义 @Bean 来覆盖自动配置的 Bean。
5. @Conditional 家族详解
@Conditional 是 Spring 4.0 引入的条件化装配机制,Spring Boot 在此基础上扩展出了一整套 @Conditional 注解。理解这套机制,是理解自动配置的前提。
5.1 @Conditional 接口定义
java
public interface Condition {
/**
* 判断条件是否满足
* @param context 条件判断上下文
* @param metadata 注解元数据
* @return true = 条件满足,注册对应的组件
*/
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}
只要 matches() 返回 false,对应标注的配置类或 @Bean 就不会生效。
5.2 常用 @Conditional 注解速查表
| 注解 | 含义 | 典型应用场景 |
|---|---|---|
@ConditionalOnBean |
容器中存在指定的 Bean | 只有用户定义了数据源,才配置 Redis |
@ConditionalOnMissingBean |
容器中不存在指定的 Bean | 提供默认的数据源实现 |
@ConditionalOnClass |
Classpath 中存在指定的类 | 只有存在 Jackson 时才配置 JSON 序列化 |
@ConditionalOnMissingClass |
Classpath 中不存在指定的类 | 如果项目中没有 Guava,才启用备用的工具类 |
@ConditionalOnProperty |
配置文件中存在对应的属性 | spring.mvc.enabled=true 时才开启 MVC |
@ConditionalOnWebApplication |
Web 应用环境下 | 只在 Web 应用中注册 DispatcherServlet |
@ConditionalOnNotWebApplication |
非 Web 应用环境下 | 在纯 CLI 应用中启动任务调度 |
5.3 实战解读:一个自动配置类的真实面貌
以 WebMvcAutoConfiguration 为例(简化后):
java
@Configuration(proxyBeanMethods = false) // ①
@ConditionalOnWebApplication(type = Type.SERVLET) // ②
@ConditionalOnClass({ Servlet.class, StandardServletServletRegistration.class, WebMvcConfigurer.class }) // ③
@ConditionalOnMissingClass("org.springframework.shell.core.CommandExecutor") // ④
@ConditionalOnBean(WebMvcProperties.class) // ⑤
@Order(0)
public class WebMvcAutoConfiguration {
@Bean
@ConditionalOnMissingBean(HtmlPageDescriber.class) // ⑥
public DefaultHtmlPageDescriber defaultHtmlPageDescriber(ObjectProvider<TemplateAvailabilityProviders> providers,
ApplicationContext context) {
return new DefaultHtmlPageDescriber(providers.getIfAvailable(), context::getResource);
}
@Bean
@ConditionalOnMissingBean(MultipartResolver.class) // ⑦
public StandardServletMultipartResolver standardServletMultipartResolver() {
return new StandardServletMultipartResolver();
}
}
逐条解释:
- ①
proxyBeanMethods = false:Spring Boot 2.2+ 的默认推荐做法。标记为false后,同一个配置类里的@Bean方法不会被 CGLIB 代理,直接调用即可。好处是减少运行时开销,坏处是你不能再用这种方法保证 Bean 的单例性。大多数自动配置类都不需要跨@Bean方法的引用,设为false是完全安全的。 - ② 仅在 Servlet 类型的 Web 应用中生效(排除了响应式应用)
- ③ 必须同时存在 Servlet、StandardServletServletRegistration、WebMvcConfigurer 这三个类------也就是说,只有 Spring MVC 相关的 Jar 都在 classpath 上时才生效
- ④ 如果存在 Shell 框架,说明你要做命令行应用而不是 Web 应用,跳过此配置
- ⑤ 必须在容器中有
WebMvcProperties类型的 Bean - ⑥⑦ 这些具体的
@Bean方法只有在用户没有自己定义对应 Bean 时才生效------这就是所谓的"可覆盖默认"设计
5.4 嵌套写法:多条件组合
Spring Boot 提供了嵌套注解来处理复杂的组合条件:
java
@Configuration
@ConditionalOnClass({RedisConnectionFactory.class})
@ConditionalOnProperty(prefix = "spring.redis", name = "host")
public class RedisAutoConfiguration {
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(name = "spring.redis.block-when-exhausted")
public LettuceConnectionFactory redisConnectionFactory(
RedisProperties properties) {
// ...
}
}
这个例子中:
- 外层条件:classpath 上有 Redis 客户端、配置里有
spring.redis.host------两项都满足时才加载整个配置类 - 内层条件:只有用户显式设置了
block-when-exhausted时,才创建特定的连接工厂
5.5 进阶:自定义 Condition
你可以实现自己的条件判断逻辑:
java
// 自定义条件:检查系统环境变量是否为特定值
public class DevEnvCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
String env = System.getenv("APP_ENV");
return "dev".equals(env);
}
}
然后使用:
java
@Configuration
@Conditional(DevEnvCondition.class)
public class DevToolsAutoConfiguration {
@Bean
public DebugServer debugServer() {
return new DebugServer(8088);
}
}
最佳实践 :生产环境中不建议使用基于环境变量的条件判断------CI/CD 场景中环境变量可能被随意设置。更推荐使用配置文件中的属性(
@ConditionalOnProperty),这样部署时无需额外操作。
6. 手写一个自定义 Starter
理论铺垫到位了,现在动手实操。我们将创建一个名为 my-sms-spring-boot-starter 的模块,实现发送短信消息时的自动装配能力。
6.1 模块结构
my-sms-spring-boot-starter/ # Starter 模块
├── pom.xml
└── src/main/java/com/example/
│ └── sms/
│ ├── SmsProperties.java # 绑定配置属性
│ ├── SmsSender.java # 短信发送器接口
│ ├── DefaultSmsSender.java # 默认实现
│ └── SmsAutoConfiguration.java # 自动配置类
└── resources/META-INF/spring/
└── org.springframework.boot.autoconfigure.AutoConfiguration.imports
6.2 编写配置属性绑定类
java
package com.example.sms;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* 绑定配置前缀 spring.sms.*
*/
@ConfigurationProperties(prefix = "sms")
public class SmsProperties {
/** 短信服务提供商的 AccessKey */
private String accessKey;
/** 短信服务提供商的 SecretKey */
private String secretKey;
/** SDK AppId */
private String sdkAppId;
/** 短信模板 ID */
private String templateId = "78392";
/** 请求超时时间(毫秒),默认 3000 */
private int timeout = 3000;
// Getter / Setter 省略
public String getAccessKey() { return accessKey; }
public void setAccessKey(String accessKey) { this.accessKey = accessKey; }
public String getSecretKey() { return secretKey; }
public void setSecretKey(String secretKey) { this.secretKey = secretKey; }
public String getSdkAppId() { return sdkAppId; }
public void setSdkAppId(String sdkAppId) { this.sdkAppId = sdkAppId; }
public String getTemplateId() { return templateId; }
public void setTemplateId(String templateId) { this.templateId = templateId; }
public int getTimeout() { return timeout; }
public void setTimeout(int timeout) { this.timeout = timeout; }
}
6.3 定义接口和默认实现
java
package com.example.sms;
/** 短信发送器统一接口 */
public interface SmsSender {
SendResult send(String phone, String content);
}
java
package com.example.sms;
/**
* 默认短信发送实现
* 实际项目中这里会对接某个真实的短信平台 SDK
*/
public class DefaultSmsSender implements SmsSender {
private final SmsProperties properties;
public DefaultSmsSender(SmsProperties properties) {
this.properties = properties;
}
@Override
public SendResult send(String phone, String content) {
// 模拟调用
return new SendResult(System.currentTimeMillis(), phone);
}
}
java
package com.example.sms;
/** 发送结果记录 */
public record SendResult(long timestamp, String phoneNumber) {}
6.4 编写自动配置类
java
package com.example.sms;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(SmsProperties.class) // ① 激活属性绑定
@ConditionalOnClass(SmsSender.class) // ② 只要 SmsSender 接口在 classpath 上
@ConditionalOnProperty(prefix = "sms", name = "enabled", havingValue = "true", matchIfMissing = true) // ③
public class SmsAutoConfiguration {
@Bean
@ConditionalOnMissingBean // ④ 允许用户自定义实现进行覆盖
public SmsSender smsSender(SmsProperties properties) {
return new DefaultSmsSender(properties);
}
}
这里的注解组合是最经典的模式:
- ①
@EnableConfigurationProperties是让@ConfigurationProperties生效的必要条件------它将 Properties 类注册为 Bean 并提供校验支持 - ② 外部使用者只要在自己的项目中声明了对
SmsSender的依赖(通常是通过引入这个 Starter),条件就满足 - ③
matchIfMissing = true表示如果没有配置sms.enabled,默认也开启------这是一种"最小惊讶"设计 - ④
@ConditionalOnMissingBean是核心:用户在项目里写了自己的SmsSenderBean,Spring Boot 就不会再创建默认的
6.5 注册 SPI 入口
# META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
com.example.sms.SmsAutoConfiguration
就这么一行。Spring Boot 启动时会找到它并尝试加载。
6.6 Pom 文件要点
xml
<?xml version="1.0" encoding="UTF-8"?>
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>my-sms-spring-boot-starter</artifactId>
<version>1.0.0-SNAPSHOT</version>
<dependencies>
<!-- 提供自动配置的基础能力 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>3.2.0</version>
<scope>provided</scope> <!-- ① provided 避免污染使用者 -->
</dependency>
<!-- 配置属性绑定依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<version>3.2.0</version>
<optional>true</optional> <!-- ② optional,不会传递到依赖方 -->
</dependency>
</dependencies>
</project>
注意:
- ①
spring-boot-starter应设置为provided------Starter 本身不需要引入 Spring Boot 运行时的依赖,因为这些应由最终使用者来管理版本 - ②
spring-boot-configuration-processor是编译时使用的注解处理器,会在打包前生成additional-spring-configuration-metadata.json,用于 IDE 配置提示
6.7 使用自定义 Starter
在其他项目中只需加依赖:
xml
<dependency>
<groupId>com.example</groupId>
<artifactId>my-sms-spring-boot-starter</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>
然后配置属性:
yaml
sms:
access-key: AKxxxxxxxxxx
secret-key: SKxxxxxxxxxx
sdk-app-id: 1400xxxxxxxx
template-id: "78392"
timeout: 5000
就可以直接在业务代码中注入使用:
java
@Service
public class OrderNotificationService {
private final SmsSender smsSender;
public OrderNotificationService(SmsSender smsSender) {
this.smsSender = smsSender;
}
public void notifyOrderPaid(String phone, long orderId) {
smsSender.send(phone, "您的订单 " + orderId + " 已支付成功");
}
}
如果你的项目需要自定义发送逻辑,只需提供一个 SmsSender 类型的 Bean 即可无缝替换默认实现------完全不需要修改任何其他代码。这就是自动配置带来的解耦效果。
7. 常见问题与最佳实践
7.1 自动配置的排序问题
多个自动配置类之间可能存在先后依赖关系。Spring Boot 提供了三种方式来控制顺序:
| 方式 | 说明 | 优先级 |
|---|---|---|
@AutoConfigureBefore |
在当前列出的配置类之前执行 | 低 |
@AutoConfigureAfter |
在当前列出的配置类之后执行 | 低 |
@AutoConfigureOrder |
明确的顺序值 | 高 |
java
@Configuration
@AutoConfigureAfter({DataSourceAutoConfiguration.class})
public class MyRepositoryAutoConfiguration {
// 确保数据源已经配置完毕后再创建 Repository
}
7.2 profile 与自动配置的联动
自动配置可以和 @Profile 一起使用:
java
@Configuration
@Profile("dev")
public class DevDatabaseAutoConfiguration {
@Bean
public DataSource dataSource() {
return EmbeddedDatabaseBuilder.builder()
.setType(H2)
.build();
}
}
当 spring.profiles.active=dev 时才会生效。这在本地开发调试时特别有用。
7.3 排查自动配置问题
Spring Boot 在启动时会打印自动配置报告(A/B 两部分:
- Positive matches:匹配并生效的配置类
- Negative matches:未匹配的原因分析
有两种方式开启:
bash
# 方式 1:命令行参数
java -jar app.jar --debug
# 方式 2:配置文件
# application.properties
debug=true
常见场景 :你以为某个自动配置类会生效但实际上没有------打开这份报告会告诉你"为什么没生效"(通常是因为某个 @Conditional 条件不满足)。
7.4 自动配置 vs 用户自定义 Bean 的优先级
记住一句话:用户自定义 Bean > 自动配置 Bean (前提是自动配置使用了 @ConditionalOnMissingBean)。
因此,要覆盖自动配置提供的 Bean,最简单的方式就是在自己项目中定义同类型的 @Bean:
java
@Bean
public ObjectMapper objectMapper() {
return new ObjectMapper()
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
}
这会覆盖 JacksonAutoConfiguration 中创建的默认 ObjectMapper。
7.5 避坑指南
坑 1:扫描范围过大导致意外匹配
@ComponentScan 默认扫描主类所在包及其子包。如果把主类放在 com.example 下,那么所有 com.example.xxx 包里的 @Component 都会被扫描到。建议主类放在唯一的根包下,比如 com.example.myapp。
坑 2:@Configuration 未设 proxyBeanMethods=false
在大型项目中,如果配置类里有很多 @Bean 方法互相调用,proxyBeanMethods=true(旧版默认值)会产生大量 CGLIB 代理,增加启动时间。Spring Boot 2.2+ 推荐统一设为 false。
坑 3:starter 依赖版本锁定问题
如果使用自己的 starter,要注意不要和用户的 Spring Boot 版本产生冲突。建议使用 ${spring-boot.version} 统一管理或者用 BOM 管理依赖版本。
坑 4:忽略 @ConditionalOnProperty 的 matchIfMissing
如果不设置 matchIfMissing,默认为 false,即没有该配置时条件不满足、配置不生效。如果你的组件应该是"有配置就用、无配置则禁用"的模式,记得加上 matchIfMissing = false(默认行为);如果是"默认启用"模式,设为 matchIfMissing = true。
8. 小结
本文系统梳理了 Spring Boot 自动配置的完整机制:
- 入口 :
@SpringBootApplication=@SpringBootConfiguration+@EnableAutoConfiguration+@ComponentScan - 核心机制 :
@EnableAutoConfiguration通过ImportSelector+ SPI 发现所有候选配置类,再通过@Conditional系列注解进行条件过滤 - SPI 文件 :Spring Boot 3.x 使用
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports - @Conditional 家族 :
OnBean、OnClass、OnProperty等是自动配置智能化的基石 - 自定义 Starter:通过配置属性绑定 + 条件化自动配置类 + SPI 注册三件套即可完成
掌握这些内容后,你已经能够理解绝大多数 Spring Boot Starter 的运作方式,也能写出高质量的自定义 Starter。
9. 下一步
本系列的下一篇文章将深入探讨 Spring Boot 起步依赖(Starters)的机制与最佳实践:
- Spring Boot 预定义了多少个 Starter?它们是怎么组织依赖的?
- Maven BOM(Bill of Materials)是如何实现"不用指定版本号"的?
- 如何给一个已有项目添加合理的起步依赖管理?
如果你对本文有任何疑问、不同的理解或有想要讨论的场景,欢迎在评论区留言。