问题引入
2023 年,某团队引入 mybatis-spring-boot-starter 后,MyBatis 立刻可用——SqlSessionFactory 自动创建、Mapper 接口自动扫描、数据源自动配置。开发者好奇:"我们什么都没配,它怎么知道要连哪个数据库?怎么知道扫描哪些 Mapper?"
更神奇的是,当团队切换到 spring-boot-starter-data-jpa 后,之前 MyBatis 的自动配置自动消失了,JPA 的 EntityManager 自动出现了。这种"引入依赖就生效、去掉依赖就消失"的魔法,让开发者既惊叹又不安——万一它自动配置了一个你不想要的 Bean 怎么办?
SpringBoot 的"约定大于配置"理念大幅提升了开发效率,但面试追问下去:
@SpringBootApplication到底由哪几个注解组成?- 自动配置是在哪个阶段加载的?
spring.factories和META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports有什么区别? @ConditionalOnClass、@ConditionalOnMissingBean是怎么判断的?- 内嵌 Tomcat 是怎么在
main方法里跑起来的? - 配置文件的加载优先级是什么?为什么
application-dev.yml会覆盖application.yml?
本文从 SpringApplication.run() 的启动流程出发,揭开 SpringBoot 自动配置的封装黑盒。
核心概念
1. @SpringBootApplication 拆解
java
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
@SpringBootApplication 是一个复合注解,等价于:
java
@Configuration // 标识这是一个配置类
@EnableAutoConfiguration // 开启自动配置
@ComponentScan // 开启组件扫描(默认扫描当前包及子包)
public @interface SpringBootApplication {}
@SpringBootApplication
│
├── @Configuration
│ └── 允许使用 @Bean 定义 Bean
│
├── @EnableAutoConfiguration
│ └── @Import(AutoConfigurationImportSelector.class)
│ └── 读取 AutoConfiguration.imports
│ └── 根据条件注解过滤
│
└── @ComponentScan
└── 扫描 @Component、@Service、@Repository、@Controller
读图导引:三层结构——@Configuration 提供配置能力,@EnableAutoConfiguration 引入自动配置的候选类并根据条件过滤,@ComponentScan 扫描业务代码中的组件。自动配置和组件扫描是两个独立的机制。
2. 自动配置的两条路线
SpringBoot 的自动配置经历了两代机制:
| 机制 | 文件位置 | 版本 | 说明 |
|---|---|---|---|
| spring.factories | META-INF/spring.factories |
2.7 及之前 | 键值对格式,所有 Starter 共用 |
| AutoConfiguration.imports | META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports |
2.7+ / 3.x | 每行一个配置类,更清晰 |
SpringBoot 2.7 引入了新的 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件格式,3.x 完全迁移到新格式,但保留了 spring.factories 的兼容。
3. 条件注解全家桶
| 条件注解 | 判断逻辑 |
|---|---|
@ConditionalOnClass |
类路径存在指定类时生效 |
@ConditionalOnMissingClass |
类路径不存在指定类时生效 |
@ConditionalOnBean |
容器中存在指定 Bean 时生效 |
@ConditionalOnMissingBean |
容器中不存在指定 Bean 时生效 |
@ConditionalOnProperty |
配置文件中存在指定属性时生效 |
@ConditionalOnWebApplication |
是 Web 应用时生效 |
@ConditionalOnExpression |
SpEL 表达式为 true 时生效 |
@ConditionalOnClass(DataSource.class)
│
▼
ClassLoader 尝试加载 DataSource
│
├── 成功 → 条件匹配 → 配置类生效
│ └── DataSourceAutoConfiguration 注册
│
└── 失败 → 条件不匹配 → 配置类跳过
└── DataSourceAutoConfiguration 不注册
读图导引:条件注解是自动配置的"阀门"——类路径检查通过,阀门打开,配置类生效;检查失败,阀门关闭,配置类跳过。这就是为什么引入 mybatis-starter 后 MyBatis 自动配置生效、去掉依赖后自动消失。
4. 配置加载优先级
SpringBoot 的配置来源按优先级从高到低:
1. 命令行参数(--server.port=8081)
2. java:comp/env 的 JNDI 属性
3. Java 系统属性(System.getProperties())
4. 操作系统环境变量
5. RandomValuePropertySource(random.int、random.long)
6. jar 包外部的 application-{profile}.yml
7. jar 包内部的 application-{profile}.yml
8. jar 包外部的 application.yml
9. jar 包内部的 application.yml
10. @PropertySource 注解定义的属性
11. SpringApplication.setDefaultProperties 设置的默认属性
读图导引:优先级从上到下递减。高优先级的配置会覆盖低优先级的同名配置。最常用的是"命令行参数 > profile 配置 > 默认配置"这条链。
原理分析
1. SpringBoot 启动流程全链路
SpringApplication.run(Application.class, args)
│
▼
┌─────────────────────────────────────────┐
│ 1. 创建 SpringApplication │
│ - 推断应用类型(Servlet/Reactive/None)│
│ - 加载 ApplicationContextInitializer │
│ - 加载 ApplicationListener │
└─────────────────┬───────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ 2. 运行 SpringApplication │
│ prepareEnvironment() │
│ - 加载配置文件 │
│ - 激活 profile │
└─────────────────┬───────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ 3. 创建 ApplicationContext │
│ - Web 应用: AnnotationConfig │
│ ServletWebServerApplicationContext │
│ - 非 Web: AnnotationConfig │
│ ApplicationContext │
└─────────────────┬───────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ 4. 准备 Context │
│ - 执行 Initializer │
│ - 加载主类 BeanDefinition │
└─────────────────┬───────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ 5. 刷新 Context(核心!) │
│ - 加载 BeanDefinition │
│ - 执行 AutoConfiguration │
│ - 创建单例 Bean │
│ - 启动内嵌 Web 服务器 │
└─────────────────┬───────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ 6. 执行 Runner │
│ - ApplicationRunner │
│ - CommandLineRunner │
└─────────────────────────────────────────┘
读图导引:启动分六步,第 5 步"刷新 Context"是核心——自动配置在此阶段加载,单例 Bean 在此创建,内嵌 Tomcat 在此启动。
refresh() 中的关键调用链:
java
// AbstractApplicationContext.refresh()
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// 1. 准备刷新(初始化环境、验证必要属性)
prepareRefresh();
// 2. 获取/刷新 BeanFactory
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// 3. 准备 BeanFactory(注册类加载器、表达式解析器等)
prepareBeanFactory(beanFactory);
try {
// 4. 子类扩展(如注册 ServletContextAwareProcessor)
postProcessBeanFactory(beanFactory);
// 5. 执行 BeanFactoryPostProcessor(关键!)
// ConfigurationClassPostProcessor 在此解析 @Configuration 类
// AutoConfigurationImportSelector 在此加载自动配置类
invokeBeanFactoryPostProcessors(beanFactory);
// 6. 注册 BeanPostProcessor
registerBeanPostProcessors(beanFactory);
// 7. 初始化 MessageSource、事件广播器等
initMessageSource();
initApplicationEventMulticaster();
// 8. 子类扩展(Web 应用在此创建内嵌服务器)
onRefresh(); // ServletWebServerApplicationContext 重写此方法
// 9. 注册监听器
registerListeners();
// 10. 实例化所有非延迟加载的单例 Bean
finishBeanFactoryInitialization(beanFactory);
// 11. 完成刷新(发布 ContextRefreshedEvent)
finishRefresh();
}
// ...
}
}
2. 自动配置的加载机制
@EnableAutoConfiguration 的入口
java
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {}
AutoConfigurationImportSelector 实现了 DeferredImportSelector,在 invokeBeanFactoryPostProcessors 阶段被调用。
读取候选配置类
java
// AutoConfigurationImportSelector.getAutoConfigurationEntry()
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
// 1. 检查是否启用自动配置
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
// 2. 获取 @EnableAutoConfiguration 的 exclude 属性
AnnotationAttributes attributes = getAttributes(annotationMetadata);
// 3. 读取所有候选配置类
// SpringBoot 2.7+: META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
// SpringBoot 2.6-: META-INF/spring.factories (key=org.springframework.boot.autoconfigure.EnableAutoConfiguration)
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
// 4. 去重
configurations = removeDuplicates(configurations);
// 5. 应用 @EnableAutoConfiguration 的 exclude 过滤
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
// 6. 按条件过滤(关键!)
configurations = getConfigurationClassFilter().filter(configurations);
// 7. 发布自动配置导入事件
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
条件过滤的底层
java
// FilteringSpringBootCondition.match()
protected final boolean match(String className, ClassLoader classLoader) {
// 每个条件注解对应一个 Condition 实现
// @ConditionalOnClass → OnClassCondition
// @ConditionalOnMissingBean → OnBeanCondition
// @ConditionalOnProperty → OnPropertyCondition
// OnClassCondition 的判断逻辑:
Class<?> candidateClass = ClassUtils.forName(className, classLoader);
// 如果类加载失败(ClassNotFoundException),条件不匹配
// 如果类加载成功,继续检查其他条件
}
3. 内嵌 Tomcat 的启动原理
Web 应用的 Context 类型
非 Web 应用 Web 应用
│ │
▼ ▼
┌─────────────────────┐ ┌─────────────────────────────────┐
│ AnnotationConfig │ │ AnnotationConfigServletWebServer │
│ ApplicationContext │ │ ApplicationContext │
│ │ │ │
│ 无 onRefresh() 扩展 │ │ onRefresh() 重写: │
│ │ │ createWebServer() │
└─────────────────────┘ │ ↓ │
│ ServletWebServerFactory │
│ ↓ │
│ WebServer.start() │
│ ↓ │
│ Tomcat.start() │
└─────────────────────────────────┘
读图导引:Web 应用使用 ServletWebServerApplicationContext,它在 onRefresh() 阶段创建并启动内嵌 Web 服务器。非 Web 应用跳过这一步。
createWebServer() 的核心逻辑
java
// ServletWebServerApplicationContext
private void createWebServer() {
WebServer webServer = this.webServer;
ServletContext servletContext = getServletContext();
if (webServer == null && servletContext == null) {
// 1. 从容器中获取 WebServerFactory(Tomcat/Jetty/Undertow)
ServletWebServerFactory factory = getWebServerFactory();
// 2. 创建 WebServer(内部创建并配置 Tomcat 实例)
this.webServer = factory.getWebServer(getSelfInitializer());
// 3. 注册优雅关闭钩子
registerShutdownHook();
}
// ...
}
java
// TomcatServletWebServerFactory.getWebServer()
public WebServer getWebServer(ServletContextInitializer... initializers) {
// 1. 创建 Tomcat 实例
Tomcat tomcat = new Tomcat();
// 2. 配置基础目录
File baseDir = (this.baseDirectory != null) ? this.baseDirectory
: createTempDir("tomcat");
tomcat.setBaseDir(baseDir.getAbsolutePath());
// 3. 创建 Connector(监听端口)
Connector connector = new Connector(this.protocol);
connector.setPort(getPort());
tomcat.getService().addConnector(connector);
// 4. 配置 Host 和 Context
prepareContext(tomcat.getHost(), initializers);
// 5. 返回 WebServer 包装对象(封装了 Tomcat 的启动和停止)
return getTomcatWebServer(tomcat);
}
为什么能内嵌?
传统 Tomcat 是独立进程,通过 catalina.sh start 启动。SpringBoot 内嵌 Tomcat 的关键是:
- Tomcat 提供了纯 Java API(
org.apache.catalina.startup.Tomcat) - SpringBoot 在
refresh()的onRefresh()阶段调用Tomcat.start() - 内嵌 Tomcat 和 Spring Context 共享同一个 JVM 进程,Servlet 容器随 Spring 容器启动而启动、关闭而关闭
4. Starter 的原理与开发
Starter 的模块结构
一个标准的 SpringBoot Starter 通常包含两个模块:
my-spring-boot-starter/ ← 依赖聚合(用户直接引入)
├── pom.xml
│ └── 依赖 my-spring-boot-autoconfigure
│
my-spring-boot-autoconfigure/ ← 自动配置实现
├── src/main/java/
│ └── com/example/autoconfigure/
│ ├── MyAutoConfiguration.java # @AutoConfiguration
│ ├── MyProperties.java # @ConfigurationProperties
│ └── MyService.java # 核心服务
├── src/main/resources/
│ └── META-INF/
│ └── spring/
│ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports
└── pom.xml
读图导引:Starter 是"门面"(只聚合依赖),真正的自动配置逻辑在 autoconfigure 模块中。autoconfigure 模块包含配置类、属性类和核心服务,通过 AutoConfiguration.imports 注册。
自定义 Starter 示例
属性类:
java
@ConfigurationProperties(prefix = "my.starter")
public class MyProperties {
private boolean enabled = true;
private String name = "default";
private int timeout = 3000;
// getters and setters
}
自动配置类:
java
@AutoConfiguration
@ConditionalOnClass(MyService.class)
@ConditionalOnProperty(prefix = "my.starter", name = "enabled", havingValue = "true", matchIfMissing = true)
@EnableConfigurationProperties(MyProperties.class)
public class MyAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public MyService myService(MyProperties properties) {
return new MyService(properties.getName(), properties.getTimeout());
}
}
注册自动配置:
# META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
com.example.autoconfigure.MyAutoConfiguration
条件注解的作用:
@ConditionalOnClass(MyService.class):类路径有 MyService 才生效@ConditionalOnProperty(...):配置my.starter.enabled=true(默认 true)才生效@ConditionalOnMissingBean:用户没自定义 MyService 时才创建默认实例
5. Actuator 与 Micrometer
SpringBoot Actuator 提供生产级的监控端点:
| 端点 | 功能 | 敏感 |
|---|---|---|
/actuator/health |
应用健康状态 | 否(默认) |
/actuator/metrics |
各项指标(JVM、CPU、内存、HTTP) | 是 |
/actuator/metrics/jvm.memory.used |
JVM 内存使用 | 是 |
/actuator/loggers |
日志级别查看和修改 | 是 |
/actuator/env |
环境属性 | 是 |
/actuator/beans |
所有 Bean | 是 |
/actuator/conditions |
自动配置报告(哪些生效、哪些跳过) | 是 |
/actuator/threaddump |
线程 dump | 是 |
/actuator/heapdump |
堆 dump | 是 |
yaml
# application.yml
management:
endpoints:
web:
exposure:
include: health,metrics,prometheus,conditions # 暴露的端点
endpoint:
health:
show-details: always # health 显示详情
metrics:
tags:
application: ${spring.application.name}
Micrometer 对接 Prometheus:
java
@Configuration
public class MetricsConfig {
@Bean
MeterRegistryCustomizer<MeterRegistry> metricsCommonTags() {
return registry -> registry.config()
.commonTags("application", "order-service");
}
}
yaml
# Prometheus 抓取配置
management:
endpoints:
web:
exposure:
include: prometheus
访问 /actuator/prometheus 即可获取 Prometheus 格式的指标数据。
实战/源码
1. 查看自动配置报告
bash
# 启动时添加 --debug 参数
java -jar app.jar --debug
或在 application.yml 中:
yaml
debug: true
启动日志中会输出自动配置报告:
Positive matches: ← 生效的自动配置
DataSourceAutoConfiguration matched:
- @ConditionalOnClass found required classes ...
Negative matches: ← 跳过的自动配置
HibernateJpaAutoConfiguration did not match:
- @ConditionalOnClass did not find required class 'org.hibernate.ejb.HibernateEntityManager'
Exclusions: ← 被排除的自动配置
None
生产环境建议直接访问 /actuator/conditions 端点查看。
2. 自定义配置覆盖自动配置
java
@Configuration
public class DataSourceConfig {
@Bean
@Primary // 标记为首选,覆盖自动配置的 DataSource
public DataSource dataSource() {
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:mysql://localhost:3306/mydb");
config.setMaximumPoolSize(20);
// ...
return new HikariDataSource(config);
}
}
由于 @ConditionalOnMissingBean(DataSource.class) 的判断,当用户自定义了 DataSource Bean,自动配置的 DataSource 就会跳过。
3. Profile 配置隔离
yaml
# application.yml(默认配置)
spring:
profiles:
active: dev
server:
port: 8080
---
# application-dev.yml(开发环境)
spring:
datasource:
url: jdbc:mysql://localhost:3306/dev_db
username: dev
password: dev123
---
# application-prod.yml(生产环境)
spring:
datasource:
url: jdbc:mysql://prod-db.internal:3306/prod_db
username: ${DB_USER}
password: ${DB_PASSWORD}
server:
port: 80
多 profile 配置在一个文件中:
yaml
# application.yml
spring:
config:
activate:
on-profile: dev
server:
port: 8080
---
spring:
config:
activate:
on-profile: prod
server:
port: 80
4. ApplicationRunner 与 CommandLineRunner
java
@Component
@Order(1) // 执行顺序
public class CacheWarmUpRunner implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) throws Exception {
// 应用启动后执行
// args 包含 --key=value 和 positional args
System.out.println("缓存预热...");
}
}
@Component
@Order(2)
public class KafkaConsumerStarter implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
// 应用启动后执行
System.out.println("启动 Kafka 消费者...");
}
}
区别:
ApplicationRunner:参数封装为ApplicationArguments,更方便解析CommandLineRunner:原始字符串数组参数
执行时机:在 refresh() 完成后、ApplicationStartedEvent 之前。
5. SpringBoot 2.x vs 3.x 迁移要点
| 变化 | 2.x | 3.x |
|---|---|---|
| JDK 要求 | JDK 8+ | JDK 17+ |
| Jakarta EE | javax.* |
jakarta.* |
| 自动配置注册 | spring.factories |
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports |
| 原生镜像 | 实验支持 | 正式支持(GraalVM) |
| Micrometer | 可选 | 内置(默认启用) |
| Observability | Sleuth | Micrometer Observation |
Jakarta 命名空间迁移:
java
// SpringBoot 2.x
import javax.servlet.http.HttpServletRequest;
import javax.persistence.Entity;
// SpringBoot 3.x
import jakarta.servlet.http.HttpServletRequest;
import jakarta.persistence.Entity;
这是 3.x 最大的破坏性变更,所有 javax.* 包名改为 jakarta.*。
常见问题
Q1:自动配置不生效,怎么排查?
答:三步排查:
-
查看自动配置报告
bashjava -jar app.jar --debug # 或访问 /actuator/conditions -
检查条件注解是否满足
@ConditionalOnClass:类路径是否有指定类?@ConditionalOnProperty:配置项是否正确?@ConditionalOnMissingBean:是否已有同名 Bean 导致跳过?
-
检查配置类是否被扫描
- 配置类是否在
AutoConfiguration.imports中注册? - 是否被
@SpringBootApplication的扫描路径覆盖?
- 配置类是否在
常见陷阱:
- 自定义配置类放在了主类包之外,未被
@ComponentScan扫描 - 引入了 Starter 但版本不兼容(如 SpringBoot 3.x 用了 2.x 的 Starter)
@ConditionalOnProperty的配置项拼写错误
Q2:为什么配置文件改了没生效?
答:检查配置加载优先级和 profile:
bash
# 查看实际生效的配置
java -jar app.jar --debug
# 日志中会输出:Loaded config file '.../application-prod.yml'
常见原因:
- 优先级被覆盖:
application-dev.yml中的配置覆盖了application.yml - 拼写错误:SpringBoot 使用宽松绑定,
server.port和SERVER_PORT等价,但server-port不等价 - 缓存问题:IDE 没有重新编译,或者
target/classes中的配置文件未更新 - Profile 未激活:
spring.profiles.active配置在靠后的配置文件中,被前面的覆盖
Q3:内嵌 Tomcat 怎么调整线程池和连接数?
答:通过 application.yml 配置:
yaml
server:
tomcat:
threads:
max: 200 # 最大工作线程数
min-spare: 10 # 最小空闲线程数
max-connections: 8192 # 最大连接数(NIO 模式下)
accept-count: 100 # 当所有线程都在忙时,队列中允许等待的连接数
connection-timeout: 20000 # 连接超时(毫秒)
Tomcat 连接处理模型:
请求到达
│
▼
Acceptor 线程接收连接
│
▼
Poller 线程管理 NIO Channel
│
▼
Executor 线程池处理请求
│
▼
Servlet 处理业务
max-connections:Tomcat 能同时处理的最大连接数(包括正在处理和排队等待的)accept-count:当max-connections达到上限后,OS 层队列中允许等待的连接数threads.max:实际处理请求的工作线程数上限
如果 max-connections + accept-count 都满了,新连接会被直接拒绝。
Q4:SpringBoot 应用怎么优雅关闭?
答:SpringBoot 2.3+ 支持优雅关闭:
yaml
server:
shutdown: graceful # 优雅关闭(默认 immediate)
spring:
lifecycle:
timeout-per-shutdown-phase: 30s # 等待活跃请求完成的最长时间
关闭流程:
- 收到关闭信号(SIGTERM)
- 停止接受新请求(Tomcat 拒绝新连接)
- 等待活跃请求处理完成(最多 30s)
- 关闭 ApplicationContext(销毁 Bean)
- JVM 退出
Kubernetes 中配合 preStop 钩子:
yaml
lifecycle:
preStop:
exec:
command: ["sh", "-c", "sleep 15"] # 等待负载均衡器摘除 Pod
Q5:Starter 和自动配置的最佳实践有哪些?
答:
- Starter 只负责聚合依赖,不写代码
- 自动配置类用
@AutoConfiguration(SpringBoot 2.7+),不要用@Configuration - 提供属性类
@ConfigurationProperties,让用户可以通过配置文件定制 - 用
@ConditionalOnMissingBean提供默认实现,允许用户覆盖 - 用
@ConditionalOnProperty提供开关,让用户可以关闭自动配置 - 不要在自动配置类上使用
@ComponentScan,它会扫描用户的包,导致不可控
java
// 好的实践
@AutoConfiguration
@ConditionalOnClass(MyService.class)
@ConditionalOnProperty(prefix = "my.starter", name = "enabled", havingValue = "true", matchIfMissing = true)
@EnableConfigurationProperties(MyProperties.class)
public class MyAutoConfiguration {
@Bean
@ConditionalOnMissingBean // 允许用户覆盖
public MyService myService(MyProperties properties) {
return new MyService(properties);
}
}
总结
SpringBoot 的"约定大于配置"不是少写了配置,而是框架帮你做了合理的默认选择:
- @SpringBootApplication 是三合一注解:@Configuration 提供配置能力,@EnableAutoConfiguration 引入自动配置,@ComponentScan 扫描业务组件。理解每个注解的职责,才能理解自动配置和组件扫描是两个独立机制
- 自动配置的本质是"条件化的 Bean 注册":容器刷新时读取所有候选配置类,根据类路径、Bean 是否存在、配置属性等条件过滤,最终决定哪些配置生效。引入 Starter 就生效、去掉就消失,是因为条件注解的检查结果变了
- 内嵌 Tomcat 的启动是 Spring 容器刷新的一部分:ServletWebServerApplicationContext 在 onRefresh() 阶段创建并启动 Tomcat,Web 服务器和 Spring 容器共生命周期
- 配置优先级从高到低:命令行参数 > 系统属性 > 环境变量 > profile 配置 > 默认配置。理解优先级才能排查"配置为什么不生效"
- Starter 开发的核心是"合理的默认 + 可覆盖的扩展":通过 @ConditionalOnMissingBean 提供默认实现,通过 @ConfigurationProperties 提供配置点,让用户既能开箱即用,又能按需定制
最后提醒:自动配置不是魔法,它是启动时帮你做的 if-then 判断。生产环境务必通过 /actuator/conditions 查看哪些配置生效了、哪些被跳过了,避免"自动配置了一个你不想要的 Bean"导致的隐性 bug。