Skip to content

Spring Boot 从零到生产级掌握

Spring Boot 不能只理解成“少写 XML”或“快速启动项目”。真正的商业后端项目里,Spring Boot 负责把 Spring IOC、Web 容器、配置体系、自动配置、日志、监控、健康检查、打包部署和扩展点组织成一个可运行、可维护、可排查的应用。

一句话建立主线:

Spring Boot = Spring 应用的工程化启动器。它用 Starter 管依赖,用自动配置装配默认 Bean,用配置体系适配环境,用内嵌容器承载 Web 请求,用 Actuator 和日志支撑生产排查。

学习目标

学完这一页,你要能做到:

  1. 解释 Spring Boot 和 Spring 的关系。
  2. 解释 @SpringBootApplication 为什么能启动应用。
  3. 解释 SpringApplication.run 每个关键步骤做什么。
  4. 解释 Starter、自动配置、条件注解、BeanDefinition 的关系。
  5. 区分 Boot 2.6及更早、Boot 2.7过渡期和 Boot 3 的自动配置候选注册机制。
  6. 解释配置文件、环境变量、命令行参数、Profile 的优先级思路。
  7. 解释内嵌 Tomcat 什么时候创建,如何接入 DispatcherServlet。
  8. 解释一个 HTTP 请求在 Boot 应用里的完整链路。
  9. 说清常见扩展点分别在什么阶段使用。
  10. 能设计统一异常、参数校验、日志 traceId、健康检查和业务指标。
  11. 能排查自动配置不生效、端口占用、启动慢、连接池耗尽、接口 404/500。

如果你已经读完主线,但还不知道怎么把自动配置、Starter、配置属性、条件注解、健康检查、指标和失败诊断落到项目里,继续做:Spring Boot 商业场景训练营

学习路线

mermaid
flowchart TD
    A["Spring 基础<br/>IOC、AOP、事务"] --> B["Boot 启动类<br/>@SpringBootApplication"]
    B --> C["Starter<br/>依赖和版本管理"]
    C --> D["自动配置<br/>条件注解和默认 Bean"]
    D --> E["配置体系<br/>yml、Profile、环境变量"]
    E --> F["Web 应用<br/>Tomcat、MVC、JSON"]
    F --> G["工程治理<br/>日志、异常、校验、Actuator"]
    G --> H["扩展点<br/>启动、Bean、Web、监控"]
    H --> I["部署排查<br/>jar、Docker、K8s、线上问题"]

这条路线不能乱。还不懂 Spring IOC 就直接背自动配置,会觉得“Boot 很玄”;还不懂配置体系就写 Starter,会把参数写死;还不懂 Actuator 和日志,上线后就只能看报错猜问题。

第一步:Spring Boot 解决了什么问题

传统 Spring Web 项目通常需要:

  1. 自己管理 Spring、Spring MVC、Jackson、Tomcat、日志、数据库驱动等依赖版本。
  2. 配置 DispatcherServlet、组件扫描、MVC 映射、消息转换器。
  3. 手动部署到外部 Tomcat。
  4. 自己处理多环境配置。
  5. 自己补健康检查和指标。

Spring Boot 把这些常见问题收敛成约定:

能力Spring Boot 做什么不这样会怎样
Starter聚合一类场景的依赖版本冲突、缺包、配置分散
自动配置按 classpath 和条件注册默认 Bean每个项目重复配置
内嵌容器jar 直接启动 Web 服务依赖外部 Tomcat 部署
配置体系支持 yml、Profile、环境变量多环境靠改代码
Actuator暴露健康、指标、线程等信息上线后不可观测
约定优于配置常见场景有默认值新项目启动成本高

Spring Boot 不是替代 Spring,而是把 Spring 的能力做成生产友好的应用框架。

第二步:最小应用为什么能启动

最小代码:

java
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

@SpringBootApplication 是组合注解,可以拆成三层理解:

注解作用
@SpringBootConfiguration说明当前类是配置类,本质上也是 Spring 配置
@EnableAutoConfiguration开启自动配置,导入自动配置候选类
@ComponentScan从启动类所在包向下扫描组件

如果启动类放错包,比如放在 com.demo.web,而 Service 在 com.demo.service 的兄弟包下,默认扫描不到 Service。正确做法通常是把启动类放在根包:

text
com.company.project
  DemoApplication.java
  controller
  service
  mapper
  config

第三步:SpringApplication.run 做了什么

简化流程:

mermaid
flowchart TD
    A["创建 SpringApplication"] --> B["推断应用类型<br/>Servlet / Reactive / 非 Web"]
    B --> C["加载监听器和初始化器"]
    C --> D["准备 Environment<br/>配置、Profile、参数"]
    D --> E["创建 ApplicationContext"]
    E --> F["加载启动类和自动配置"]
    F --> G["refresh 容器<br/>创建 Bean"]
    G --> H["启动内嵌 WebServer"]
    H --> I["执行 Runner"]
    I --> J["发布启动完成事件"]

关键点:

步骤为什么重要
准备 Environment条件注解、配置绑定、Profile 都依赖它
创建 ApplicationContextBoot 最终仍然是 Spring 容器
加载自动配置默认 Web、JSON、数据源、事务等能力来自这里
refreshBeanDefinition 变成真正 Bean
启动 WebServerTomcat/Jetty/Undertow 开始监听端口
Runner容器可用后做启动检查或轻量预热

第四步:Starter 为什么能开箱即用

Starter 本身通常不写大量业务代码,它的价值是“把某类场景依赖聚合起来”。

例如 spring-boot-starter-web 通常会带来:

  1. Spring MVC。
  2. Jackson JSON。
  3. 校验相关依赖。
  4. 内嵌 Tomcat。
  5. Web 自动配置所需依赖。
mermaid
flowchart TD
    A["业务项目引入 starter"] --> B["Maven/Gradle 拉取依赖"]
    B --> C["classpath 出现相关类"]
    C --> D["自动配置条件匹配"]
    D --> E["注册默认 Bean"]
    E --> F["业务可以直接使用 Web 能力"]

Starter + 自动配置才是完整开箱即用。如果只有依赖没有自动配置,业务仍然要手动声明大量 Bean。

第五步:自动配置不是魔法

自动配置的核心公式:

text
候选自动配置类 + 条件注解 + 默认 Bean + 用户 Bean 优先 = 自动配置

简化流程:

mermaid
flowchart TD
    A["@EnableAutoConfiguration"] --> B["AutoConfigurationImportSelector"]
    B --> C["读取自动配置候选类"]
    C --> D["排除、去重、排序"]
    D --> E["解析条件注解"]
    E --> F{"条件是否匹配"}
    F -- "匹配" --> G["注册 BeanDefinition"]
    F -- "不匹配" --> H["跳过该配置"]
    G --> I["refresh 阶段创建 Bean"]

常见条件注解:

注解作用
@ConditionalOnClassclasspath 有某个类才生效
@ConditionalOnMissingClassclasspath 没有某个类才生效
@ConditionalOnBean容器里已有某个 Bean 才生效
@ConditionalOnMissingBean容器里没有某个 Bean 才创建默认 Bean
@ConditionalOnProperty配置项满足条件才生效
@ConditionalOnWebApplicationWeb 应用才生效

Boot 2 和 Boot 3 的区别

版本自动配置声明方式
Spring Boot 2.6及更早常见 META-INF/spring.factories
Spring Boot 2.7已支持并推荐 AutoConfiguration.imports,仍兼容旧候选注册方式
Spring Boot 3使用 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

Boot 2 示例:

properties
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.demo.DemoAutoConfiguration

Boot 3 示例:

text
com.example.demo.DemoAutoConfiguration

面试时不要说“spring.factories 一定错了”。老项目、老版本和部分兼容场景仍可能看到它。

第六步:自定义 Starter 的最小 Demo

目标:引入一个 starter 后,自动注册一个 TraceIdGenerator

自动配置类:

java
package com.example.trace.autoconfigure;

import org.springframework.boot.autoconfigure.AutoConfiguration;
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;

@AutoConfiguration
@EnableConfigurationProperties(TraceProperties.class)
@ConditionalOnProperty(prefix = "demo.trace", name = "enabled", havingValue = "true", matchIfMissing = true)
public class TraceAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean
    public TraceIdGenerator traceIdGenerator(TraceProperties properties) {
        return new TraceIdGenerator(properties.getPrefix());
    }
}

配置属性:

java
package com.example.trace.autoconfigure;

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties(prefix = "demo.trace")
public class TraceProperties {
    private boolean enabled = true;
    private String prefix = "trace";

    public boolean isEnabled() {
        return enabled;
    }

    public void setEnabled(boolean enabled) {
        this.enabled = enabled;
    }

    public String getPrefix() {
        return prefix;
    }

    public void setPrefix(String prefix) {
        this.prefix = prefix;
    }
}

业务 Bean:

java
package com.example.trace.autoconfigure;

import java.util.UUID;

public class TraceIdGenerator {
    private final String prefix;

    public TraceIdGenerator(String prefix) {
        this.prefix = prefix;
    }

    public String nextId() {
        return prefix + "-" + UUID.randomUUID();
    }
}

Boot 3 自动配置声明文件:

text
# META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
com.example.trace.autoconfigure.TraceAutoConfiguration

业务项目配置:

yaml
demo:
  trace:
    enabled: true
    prefix: order

为什么要加 @ConditionalOnMissingBean

  1. 公共 starter 提供默认实现。
  2. 业务项目如果自己声明 TraceIdGenerator,默认实现自动让位。
  3. 否则会出现 Bean 冲突,或者业务无法替换默认逻辑。

第七步:配置体系怎么工作

Spring Boot 配置来源很多:

mermaid
flowchart TD
    A["默认配置"] --> B["application.yml"]
    B --> C["application-{profile}.yml"]
    C --> D["环境变量"]
    D --> E["JVM 参数"]
    E --> F["命令行参数"]
    F --> G["配置中心"]

不同版本和场景优先级细节很多,零基础先抓住原则:

  1. 越外部的配置通常越适合覆盖内部默认值。
  2. 越靠近本次启动命令的配置通常优先级越高。
  3. Profile 用来区分环境,不要把 dev、test、prod 混在一起。
  4. 密码、密钥不要提交到仓库。

@Value@ConfigurationProperties

方式适合
@Value("${demo.timeout}")少量简单配置
@ConfigurationProperties一组配置、嵌套配置、类型绑定、校验

推荐写法:

java
import jakarta.validation.constraints.Min;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.validation.annotation.Validated;

@Validated
@ConfigurationProperties(prefix = "collect")
public class CollectProperties {
    @Min(1)
    private int threadSize = 8;
    private int timeoutSeconds = 10;

    public int getThreadSize() {
        return threadSize;
    }

    public void setThreadSize(int threadSize) {
        this.threadSize = threadSize;
    }

    public int getTimeoutSeconds() {
        return timeoutSeconds;
    }

    public void setTimeoutSeconds(int timeoutSeconds) {
        this.timeoutSeconds = timeoutSeconds;
    }
}

配置绑定失败要尽早失败,而不是上线后线程池参数变成默认值才发现。

第八步:内嵌 Tomcat 如何启动

Servlet Web 应用中,Boot 会创建 ServletWebServerApplicationContext。在容器刷新阶段,它会通过 ServletWebServerFactory 创建并启动 Tomcat。

mermaid
flowchart TD
    A["refresh ApplicationContext"] --> B["创建 ServletWebServerFactory"]
    B --> C["创建 Tomcat WebServer"]
    C --> D["注册 DispatcherServlet"]
    D --> E["注册 Filter、Listener、Servlet"]
    E --> F["Tomcat 监听端口"]
    F --> G["接收 HTTP 请求"]

如果端口被占用:

text
Web server failed to start. Port 8080 was already in use.

排查:

bash
netstat -ano | findstr 8080

然后处理占用进程,或修改:

yaml
server:
  port: 8081

第九步:一次 HTTP 请求怎么走

mermaid
flowchart TD
    A["浏览器 / 前端 / 调用方"] --> B["Tomcat Connector"]
    B --> C["Filter 链"]
    C --> D["DispatcherServlet"]
    D --> E["HandlerMapping 找 Controller"]
    E --> F["参数解析和校验"]
    F --> G["Controller 方法"]
    G --> H["Service 事务和业务"]
    H --> I["Mapper / Repository"]
    I --> J["数据库 / Redis / MQ"]
    J --> I
    I --> H
    H --> K["返回 DTO"]
    K --> L["JSON 序列化"]
    L --> M["HTTP 响应"]

各环节常见问题:

环节常见问题
FiltertraceId 丢失、跨域、编码、认证异常
HandlerMapping路径不匹配导致 404
参数解析JSON 格式错、日期格式错、枚举转换失败
参数校验必填缺失、范围不合法
Controller把业务和 SQL 全写在接口层
Service事务边界不清,自调用导致事务失效
Mapper慢 SQL、N+1、连接池耗尽
JSON 序列化循环引用、时间格式不一致、字段泄露

第十步:Web 工程必须补齐的基础能力

统一返回

java
public record ApiResult<T>(int code, String message, T data) {
    public static <T> ApiResult<T> ok(T data) {
        return new ApiResult<>(0, "ok", data);
    }

    public static <T> ApiResult<T> fail(int code, String message) {
        return new ApiResult<>(code, message, null);
    }
}

参数校验

java
import jakarta.validation.constraints.NotBlank;

public record CreateAssetRequest(
    @NotBlank(message = "资产编码不能为空")
    String assetCode,
    @NotBlank(message = "资产名称不能为空")
    String name
) {
}

Controller:

java
import jakarta.validation.Valid;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/assets")
public class AssetController {

    @PostMapping
    public ApiResult<String> create(@Valid @RequestBody CreateAssetRequest request) {
        return ApiResult.ok("created:" + request.assetCode());
    }
}

统一异常

java
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ApiResult<Void> handleValidException(MethodArgumentNotValidException ex) {
        String message = ex.getBindingResult().getFieldErrors().get(0).getDefaultMessage();
        return ApiResult.fail(400, message);
    }

    @ExceptionHandler(Exception.class)
    public ApiResult<Void> handleException(Exception ex) {
        return ApiResult.fail(500, "系统异常");
    }
}

为什么不能每个 Controller 自己 try-catch:

  1. 响应格式会不统一。
  2. 日志会分散。
  3. 错误码不可治理。
  4. 安全信息可能被直接返回给前端。

第十一步:常见扩展点怎么选

扩展点阶段适合做什么
EnvironmentPostProcessorEnvironment 准备阶段配置解密、额外配置源
ApplicationContextInitializerContext refresh 前初始化上下文属性
BeanDefinitionRegistryPostProcessorBeanDefinition 阶段动态注册 BeanDefinition
BeanFactoryPostProcessorBean 创建前修改 BeanDefinition 属性
BeanPostProcessorBean 初始化前后代理、增强、注入特殊逻辑
ApplicationRunner启动完成后启动检查、轻量预热
FilterServlet 链路最前traceId、CORS、认证前置处理
HandlerInterceptorSpring MVC 链路权限、审计、接口日志
HandlerMethodArgumentResolverController 参数解析注入当前用户、租户信息
HealthIndicatorActuator 健康检查检查下游依赖状态
MeterBinder指标注册暴露业务指标
FailureAnalyzer启动失败诊断输出更友好的失败原因

扩展点选择原则:

mermaid
flowchart TD
    A["想扩展 Boot"] --> B{"影响配置读取吗"}
    B -- "是" --> C["EnvironmentPostProcessor"]
    B -- "否" --> D{"影响 Bean 定义吗"}
    D -- "是" --> E["BeanFactory / Registry 后置处理器"]
    D -- "否" --> F{"影响请求链路吗"}
    F -- "是" --> G["Filter / Interceptor / ArgumentResolver"]
    F -- "否" --> H{"影响监控诊断吗"}
    H -- "是" --> I["HealthIndicator / MeterBinder / FailureAnalyzer"]
    H -- "否" --> J["Runner 或普通 Bean"]

不要为了“高级”乱用扩展点。能用普通 Bean 解决,就不要上早期生命周期扩展点;否则排查成本会很高。

第十二步:Actuator 和生产可观测

Actuator 常用能力:

端点作用
/actuator/health健康检查
/actuator/metrics指标
/actuator/env环境属性
/actuator/beansBean 列表
/actuator/conditions自动配置条件报告
/actuator/threaddump线程栈
/actuator/loggers日志级别

生产环境不要把所有端点裸露出来。建议:

  1. 只暴露必要端点。
  2. 管理端口和业务端口分离。
  3. 接入 Spring Security 或网关权限。
  4. 禁止对公网暴露敏感端点。
  5. 指标进入 Prometheus、日志进入 ELK 或 Loki。

自定义健康检查:

java
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.stereotype.Component;

@Component
public class CollectHealthIndicator implements HealthIndicator {
    @Override
    public Health health() {
        boolean ok = true;
        if (ok) {
            return Health.up().withDetail("collect", "ready").build();
        }
        return Health.down().withDetail("collect", "not ready").build();
    }
}

第十三步:部署和运行

jar 启动

bash
java -jar app.jar --spring.profiles.active=prod --server.port=8080

JVM 参数

bash
java -Xms512m -Xmx512m -jar app.jar

Dockerfile 示例

dockerfile
FROM eclipse-temurin:17-jre
WORKDIR /app
COPY target/app.jar /app/app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "/app/app.jar"]

生产注意:

  1. 配置不要打死在镜像里。
  2. 日志输出到标准输出或统一目录。
  3. 健康检查要接入探针。
  4. JVM 内存要结合容器限制设置。
  5. 启动参数、Profile、密钥由环境注入。

线上排查总流程

mermaid
flowchart TD
    A["Spring Boot 应用异常"] --> B{"启动失败还是运行异常"}
    B -- "启动失败" --> C["看启动日志和 FailureAnalyzer"]
    C --> D["检查端口、配置、Bean、自动配置条件"]
    B -- "运行异常" --> E{"接口慢、报错还是不可用"}
    E -- "接口慢" --> F["看日志 traceId、线程、DB、Redis、下游"]
    E -- "接口报错" --> G["看异常栈、参数、业务状态"]
    E -- "不可用" --> H["看 health、连接池、线程池、GC"]
    D --> I["修复配置或依赖"]
    F --> J["定位瓶颈并限流/优化"]
    G --> K["修复代码或数据"]
    H --> L["扩容、降级或重启恢复"]

自动配置不生效

排查顺序:

  1. 是否引入了正确 starter。
  2. 自动配置类是否在候选列表。
  3. 是否被 exclude。
  4. 条件注解是否匹配。
  5. 配置项是否写错。
  6. 是否已有用户 Bean 导致默认 Bean 不创建。
  7. Bean 创建是否失败。

打开调试:

yaml
debug: true

或者看:

text
/actuator/conditions

启动慢

常见原因:

原因排查
Bean 初始化慢看启动日志、临时增加耗时统计
外部连接慢数据库、Redis、MQ、注册中心超时
扫描范围太大检查启动类包路径和组件扫描
Runner 做重活把长任务异步化或延后执行
日志阻塞检查日志输出和磁盘

接口 404

检查:

  1. Controller 是否被扫描。
  2. 路径和 HTTP 方法是否正确。
  3. context-path 是否配置。
  4. 网关是否转发到正确路径。
  5. 是否被 Security 拦截后表现为 404。

接口 500

检查:

  1. 全局异常日志有没有完整 traceId。
  2. 请求参数是否符合格式。
  3. Service 是否抛业务异常。
  4. 数据库是否唯一键冲突、锁等待、连接超时。
  5. 下游接口是否超时。

商业场景落地

医疗数据采集平台

Spring Boot 可以这样落地:

能力用法
配置体系医院接口地址、采集线程数、超时时间按环境配置
自动配置数据源、Redis、MQ、Web、JSON 默认装配
扩展点HandlerInterceptor 做租户和操作审计
Actuator暴露健康检查给 K8s 或网关
统一异常采集失败返回统一错误码,日志保留 traceId
配置属性CollectProperties 管采集批次、并发、重试
Runner启动后做轻量依赖检查,不做耗时采集

订单系统

Spring Boot 可以这样落地:

  1. Controller 只做参数校验和接口编排入口。
  2. Service 管事务边界。
  3. Mapper 只负责 SQL。
  4. 支付、库存、消息客户端做成配置化 Bean。
  5. 订单状态流转失败时统一异常和日志。
  6. Actuator 指标暴露订单创建 TPS、失败数、下游耗时。

面试标准回答

Spring Boot 是什么

text
Spring Boot 是基于 Spring 的应用工程化框架。Spring 提供 IOC、AOP、事务、MVC 等基础能力,Spring Boot 通过 Starter、自动配置、配置体系、内嵌 Web 容器和 Actuator,把这些能力组织成开箱即用、可部署、可观测的应用。它不是替代 Spring,而是降低 Spring 项目的配置、依赖和生产治理成本。

自动配置原理

text
自动配置不是魔法。Spring Boot 通过 @EnableAutoConfiguration 导入 AutoConfigurationImportSelector,读取自动配置候选类。Boot 2.6及更早常见 spring.factories,Boot 2.7 已支持并推荐 AutoConfiguration.imports,Boot 3 使用 imports 机制。候选配置类再通过 @ConditionalOnClass、@ConditionalOnMissingBean、@ConditionalOnProperty 等条件注解决定是否注册 BeanDefinition,最后仍然由 Spring IOC 在 refresh 阶段创建 Bean。

Spring Boot 启动流程

text
Spring Boot 启动大致是:创建 SpringApplication,推断应用类型,加载监听器和初始化器,准备 Environment,创建 ApplicationContext,加载启动类和自动配置,refresh 容器创建 Bean,Servlet Web 应用再启动内嵌 Tomcat,最后执行 Runner 并发布启动完成事件。

自定义 Starter 怎么设计

text
自定义 Starter 通常拆成 starter 和 autoconfigure。starter 负责聚合依赖,autoconfigure 提供自动配置类、配置属性类和默认 Bean。自动配置要用条件注解控制生效,用 @ConfigurationProperties 承载外部配置,用 @ConditionalOnMissingBean 尊重业务自定义,并通过 AutoConfiguration.imports 或 spring.factories 声明。

关联知识点

知识点继续学习
自动配置与扩展点全过程自动配置深度页
自动配置原理自动配置原理
Starter 机制Starter机制
配置体系配置体系
启动原理启动原理
扩展点Spring Boot扩展点
ActuatorActuator监控
生产监控与线上排查线上问题排查Runbook
Spring Boot Admin多实例集中监控与管理
Spring IOCSpring IOC
Spring 事务Spring事务
Spring Boot 面试面试题

本章小结

Spring Boot 要学到生产级,必须把启动流程、自动配置、Starter、配置绑定、内嵌 Web 容器、请求链路、扩展点、Actuator、部署和排查串起来。只会写 Controller 不是掌握 Boot;能解释“为什么引入一个 starter 就有默认能力、为什么自定义 Bean 后默认配置让位、为什么配置能绑定、为什么 Tomcat 能随 jar 启动、为什么 Actuator 能定位问题”,才是真正理解。