问题引入

2023 年,某团队引入 mybatis-spring-boot-starter 后,MyBatis 立刻可用——SqlSessionFactory 自动创建、Mapper 接口自动扫描、数据源自动配置。开发者好奇:"我们什么都没配,它怎么知道要连哪个数据库?怎么知道扫描哪些 Mapper?"

更神奇的是,当团队切换到 spring-boot-starter-data-jpa 后,之前 MyBatis 的自动配置自动消失了,JPA 的 EntityManager 自动出现了。这种"引入依赖就生效、去掉依赖就消失"的魔法,让开发者既惊叹又不安——万一它自动配置了一个你不想要的 Bean 怎么办?

SpringBoot 的"约定大于配置"理念大幅提升了开发效率,但面试追问下去:

  • @SpringBootApplication 到底由哪几个注解组成?
  • 自动配置是在哪个阶段加载的?spring.factoriesMETA-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:自动配置不生效,怎么排查?

:三步排查:

  1. 查看自动配置报告

    bash 复制代码
    java -jar app.jar --debug
    # 或访问 /actuator/conditions
  2. 检查条件注解是否满足

    • @ConditionalOnClass:类路径是否有指定类?
    • @ConditionalOnProperty:配置项是否正确?
    • @ConditionalOnMissingBean:是否已有同名 Bean 导致跳过?
  3. 检查配置类是否被扫描

    • 配置类是否在 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'

常见原因:

  1. 优先级被覆盖application-dev.yml 中的配置覆盖了 application.yml
  2. 拼写错误:SpringBoot 使用宽松绑定,server.portSERVER_PORT 等价,但 server-port 不等价
  3. 缓存问题:IDE 没有重新编译,或者 target/classes 中的配置文件未更新
  4. 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  # 等待活跃请求完成的最长时间

关闭流程

  1. 收到关闭信号(SIGTERM)
  2. 停止接受新请求(Tomcat 拒绝新连接)
  3. 等待活跃请求处理完成(最多 30s)
  4. 关闭 ApplicationContext(销毁 Bean)
  5. JVM 退出

Kubernetes 中配合 preStop 钩子:

yaml 复制代码
lifecycle:
  preStop:
    exec:
      command: ["sh", "-c", "sleep 15"]  # 等待负载均衡器摘除 Pod

Q5:Starter 和自动配置的最佳实践有哪些?

  1. Starter 只负责聚合依赖,不写代码
  2. 自动配置类用 @AutoConfiguration(SpringBoot 2.7+),不要用 @Configuration
  3. 提供属性类 @ConfigurationProperties,让用户可以通过配置文件定制
  4. @ConditionalOnMissingBean 提供默认实现,允许用户覆盖
  5. @ConditionalOnProperty 提供开关,让用户可以关闭自动配置
  6. 不要在自动配置类上使用 @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 的"约定大于配置"不是少写了配置,而是框架帮你做了合理的默认选择:

  1. @SpringBootApplication 是三合一注解:@Configuration 提供配置能力,@EnableAutoConfiguration 引入自动配置,@ComponentScan 扫描业务组件。理解每个注解的职责,才能理解自动配置和组件扫描是两个独立机制
  2. 自动配置的本质是"条件化的 Bean 注册":容器刷新时读取所有候选配置类,根据类路径、Bean 是否存在、配置属性等条件过滤,最终决定哪些配置生效。引入 Starter 就生效、去掉就消失,是因为条件注解的检查结果变了
  3. 内嵌 Tomcat 的启动是 Spring 容器刷新的一部分:ServletWebServerApplicationContext 在 onRefresh() 阶段创建并启动 Tomcat,Web 服务器和 Spring 容器共生命周期
  4. 配置优先级从高到低:命令行参数 > 系统属性 > 环境变量 > profile 配置 > 默认配置。理解优先级才能排查"配置为什么不生效"
  5. Starter 开发的核心是"合理的默认 + 可覆盖的扩展":通过 @ConditionalOnMissingBean 提供默认实现,通过 @ConfigurationProperties 提供配置点,让用户既能开箱即用,又能按需定制

最后提醒:自动配置不是魔法,它是启动时帮你做的 if-then 判断。生产环境务必通过 /actuator/conditions 查看哪些配置生效了、哪些被跳过了,避免"自动配置了一个你不想要的 Bean"导致的隐性 bug。