CC 咖啡猫的工作空间 Coding Space

Spring Boot 深入原理

Spring Boot 的核心价值:约定大于配置,让你用最少的配置跑起来。理解自动配置原理,才能在出问题时有方向排查,才能自定义 Starter。


1. 自动配置原理

1.1 @SpringBootApplication 的三合一

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration          // 标注这是一个配置类
@EnableAutoConfiguration // 开启自动配置
@ComponentScan          // 组件扫描(扫当前包及子包)
public @interface SpringBootApplication {}

核心是 @EnableAutoConfiguration

1.2 @EnableAutoConfiguration 做了什么

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AutoConfigurationImportSelector.class)  // 关键:导入自动配置选择器
public @interface EnableAutoConfiguration {}

AutoConfigurationImportSelector 的工作:

Spring Boot 启动
    ↓
加载 spring-boot-autoconfigure.jar
    ↓
扫描 META-INF/spring.factories
    ↓
读取 EnableAutoConfiguration 对应的配置类列表
    ↓
过滤掉不满足 @Conditional 条件的配置类
    ↓
剩余的配置类 → 当作 @Configuration 加载到容器

1.3 spring.factories 文件

spring-boot-autoconfigure-*.jar 中的 META-INF/spring.factories

# 自动配置类列表
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.autoconfigure.HelloAutoConfiguration,\
com.example.autoconfigure.DataSourceAutoConfiguration,\
...

Spring Boot 2.7+ 改为 META-INF/spring/org.springframework.boot.autoconfigure.EnableAutoConfiguration(SPI 文件)。

1.4 @Conditional 过滤条件

配置类注册之前,Spring 会检查条件:

@Configuration
@ConditionalOnClass(DataSource.class)           // 类路径有 DataSource 才加载
@ConditionalOnProperty(prefix = "app", name = "enabled", havingValue = "true")  // 配置开启
@ConditionalOnMissingBean(DataSource.class)     // 容器里没有 DataSource 才加载
public class DataSourceAutoConfiguration {
    @Bean
    public DataSource dataSource() {
        return new DruidDataSource();
    }
}

常见的 @Conditional 注解

注解 条件
@ConditionalOnClass 类路径存在某类
@ConditionalOnMissingClass 类路径不存在某类
@ConditionalOnProperty 配置属性满足条件
@ConditionalOnBean 容器中存在某 Bean
@ConditionalOnMissingBean 容器中不存在某 Bean
@ConditionalOnWebApplication 是 Web 应用

2. Spring Boot Starter 机制

2.1 Starter 的标准结构

my-starter/
├── src/main/java/com/example/mystarter/
│   ├── MyAutoConfiguration.java        # 自动配置类
│   └── MyProperties.java               # 配置属性类
└── src/main/resources/META-INF/
    └── spring/org.springframework.boot.autoconfigure.EnableAutoConfiguration
        └── com.example.mystarter.MyAutoConfiguration

2.2 自动配置类的写法

// 1. 配置属性类:绑定配置前缀
@ConfigurationProperties(prefix = "app.my-starter")
public class MyProperties {
    private String name = "default";     // 默认值
    private int timeout = 3000;

// 2. 自动配置类:根据条件注册 Bean
@Configuration
@EnableConfigurationProperties(MyProperties.class)  // 开启属性绑定
@ConditionalOnClass(MyService.class)               // 依赖的类存在才加载
@ConditionalOnProperty(prefix = "app.my-starter", name = "enabled", havingValue = "true", matchIfMissing = true)
public class MyAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean          // 没有这个 Bean 才注册
    public MyService myService(MyProperties props) {
        return new MyService(props.getName(), props.getTimeout());
    }
}

2.3 用户如何使用 Starter

# 用户只需在 application.yml 中配置
app:
  my-starter:
    enabled: true
    name: custom-name
    timeout: 5000

零 Java 代码,配置即生效。


3. SpringApplication 启动流程

3.1 启动流程图

SpringApplication.run()
    ↓
创建 SpringApplication 对象
    ↓
1. 推断 Web 应用类型(reactive / servlet / none)
2. 加载 spring.factories 的 ApplicationContextInitializer
3. 加载 spring.factories 的 ApplicationListener
4. 推断主启动类(从堆栈确定 main 所在的类)
    ↓
run(args)
    ↓
创建 BootstrapContext(引导上下文)
    ↓
配置 Headless 属性
    ↓
获取并运行 SpringApplicationRunListener( Starting 事件)
    ↓
创建并配置 Environment
    ↓
打印 Banner(启动图案)
    ↓
创建 ApplicationContext
    ↓
准备 Context:
    1. 设置 Environment
    2. 加载 source(主配置类)
    3. 执行 initializer
    4. 加载 Bean 定义(@Configuration 注解的类)
    ↓
刷新 Context:
    1. 刷新容器(加载所有 Bean)
    2. 执行 BeanFactoryPostProcessor
    3. 自动配置(@EnableAutoConfiguration)
    4. 注册 Listener(ApplicationListener)
    ↓
执行 Runner:
    ApplicationRunner + CommandLineRunner
    ↓
发布 ApplicationReadyEvent 事件
    ↓
返回 ApplicationContext

3.2 SpringApplicationRunListener

生命周期的各个节点会触发对应事件:

阶段 方法 触发时机
Starting starting() 应用刚启动
Environment environmentPrepared() Environment 准备完成
Context contextPrepared() Context 创建完成
Loaded contextLoaded() Context 加载完成
Started started() Context 启动完成
Ready ready() 应用就绪

自定义 RunListener 可用于:

  • 启动流程日志
  • 自定义健康检查
  • 初始化任务

4. Spring Boot 内嵌服务器

4.1 自动选择机制

Spring Boot 2.x 根据类路径判断:

Tomcat 类存在 → Tomcat(默认)
    ↓ 不存在
Jetty 类存在 → Jetty
    ↓ 不存在
Undertow 类存在 → Undertow
    ↓ 不存在
非 Web 应用

排除默认 Tomcat:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jetty</artifactId>
</dependency>

4.2 常用配置

server:
  port: 8080
  servlet:
    context-path: /api
  tomcat:
    threads:
      max: 200                    # 最大线程数
      min-spare: 10               # 最小空闲线程
    accept-count: 100             # 满后最大等待队列
    max-connections: 10000        # 最大连接数

5. Spring Boot Actuator 监控

5.1 引入 Actuator

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

5.2 常用端点

端点 说明 访问
/actuator/health 健康检查 {"status": "UP"}
/actuator/info 应用信息 自定义
/actuator/metrics 指标数据 CPU、内存、JVM 等
/actuator/env 环境变量 所有配置
/actuator/beans 容器中所有 Bean Bean 列表
/actuator/mappings 所有 URL 映射 REST API 列表
/actuator/trace 最近 HTTP 请求 请求追踪
/actuator/heapdump 堆Dump(需认证) 下载

5.3 暴露端点配置

management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics
        exclude: env,beans
  endpoint:
    health:
      show-details: when_authorized  # 只给授权用户看详情

5.4 自定义健康检查

@Component
public class MyHealthIndicator implements HealthIndicator {

    @Override
    public Health health() {
        boolean healthy = checkMyService();
        if (healthy) {
            return Health.up()
                .withDetail("key", "value")
                .build();
        }
        return Health.down()
            .withDetail("error", "service unavailable")
            .build();
    }
}

6. Spring Boot 异常处理

6.1 错误处理流程

请求到达 DispatcherServlet
    ↓
HandlerExceptionResolver 处理异常
    ↓
默认异常处理:
  @ExceptionHandler → Controller 内处理
  @ControllerAdvice → 全局异常处理
  DefaultHandlerExceptionResolver → Spring 标准异常
  ErrorMvcAutoConfiguration → /error 页面

6.2 @ControllerAdvice 全局异常处理

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(BusinessException.class)
    public Result<?> handleBusiness(BusinessException e) {
        return Result.error(e.getCode(), e.getMessage());
    }

    @ExceptionHandler(Exception.class)
    public Result<?> handleOther(Exception e) {
        return Result.error(500, "服务器内部错误");
    }
}

6.3 自定义 ErrorAttributes

@Component
public class MyErrorAttributes extends DefaultErrorAttributes {

    @Override
    public Map<String, Object> getErrorAttributes(
            WebRequest webRequest, ErrorAttributeOptions options) {
        Map<String, Object> attrs = super.getErrorAttributes(webRequest, options);
        attrs.put("custom", "custom-info");  // 添加自定义字段
        return attrs;
    }
}

7. 条件注册 Bean 的实际应用

7.1 多个数据源下的动态切换

@Configuration
public class DataSourceConfiguration {

    @Bean
    @Primary                      // 默认数据源
    @ConditionalOnProperty(name = "db.primary", havingValue = "mysql")
    public DataSource mysqlDataSource() { ... }

    @Bean
    @ConditionalOnProperty(name = "db.primary", havingValue = "postgres")
    public DataSource postgresDataSource() { ... }
}

7.2 根据环境激活不同配置

@Configuration
@Profile("prod")          // 只有 prod 环境才加载
public class ProdConfiguration {
    @Bean
    public DataSource prodDataSource() { ... }
}

8. 常用 Spring Boot 技巧

8.1 启动失败自动重启

spring-boot-devtools:代码修改后自动重启应用(不是热替换,是重启)。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
</dependency>

8.2 Banner 自定义

# 或者创建 src/main/resources/banner.txt
spring:
  main:
    banner-mode: console

8.3 多环境配置切换

src/main/resources/
├── application.yml           # 默认配置
├── application-dev.yml      # 开发环境
├── application-test.yml     # 测试环境
└── application-prod.yml     # 生产环境
# application.yml 中指定激活
spring:
  profiles:
    active: dev