Spring Boot 从零到精通验收清单
这页不是“背几个注解”的清单,而是用来验收你是否真正理解 Spring Boot:为什么它能快速启动,自动配置到底怎么生效,Starter 为什么能开箱即用,扩展点应该放在哪个生命周期阶段,线上出了问题怎么定位。
学习 Spring Boot 的核心目标不是会写 @RestController,而是能回答下面这些问题:
- 一个应用从
main方法到端口可访问,中间发生了什么。 - 一个默认 Bean 为什么有时会出现,有时又被你自己的 Bean 替换。
- 配置文件、环境变量、命令行参数、配置中心最终谁生效。
- 内嵌 Tomcat 什么时候创建,HTTP 请求如何进入 Controller。
- 扩展点应该怎么选,选错会导致什么问题。
- 商业系统里如何把公共客户端、监控、错误诊断沉淀成可复用 Starter。
最终目标
学完本页和关联知识点,你应该能做到:
| 能力 | 达标标准 |
|---|---|
| 会用 | 能独立搭建 REST 服务,接入数据库、Redis、MQ、配置、日志、监控 |
| 懂原理 | 能画出启动流程、自动配置流程、请求链路、配置绑定链路 |
| 会扩展 | 能选择合适扩展点解决配置解密、默认 Bean、健康检查、指标、失败诊断 |
| 能排查 | 能通过 conditions、env、configprops、metrics、日志、线程栈定位问题 |
| 能面试 | 能把标准回答和底层原理、项目落地、失败边界串起来 |
总路线
flowchart TD
A["认识 Spring Boot 的工程化价值"] --> B["理解 SpringApplication.run 启动主线"]
B --> C["理解配置加载与 Environment"]
C --> D["理解自动配置候选类如何导入"]
D --> E["理解条件注解如何决定 Bean 是否注册"]
E --> F["理解 refresh 创建 Bean 和内嵌 Tomcat"]
F --> G["理解一次 HTTP 请求链路"]
G --> H["掌握扩展点选择和生产排查"]阶段 1:Spring Boot 到底解决什么问题
是什么
Spring Boot 是基于 Spring 的应用工程化框架。它不替代 Spring IOC、AOP、事务、MVC,而是把常用工程能力封装成约定:依赖版本、自动配置、内嵌容器、外部化配置、监控端点、打包运行。
传统 Spring 项目经常需要手动做这些事:
- 写大量 XML 或 Java Config。
- 自己处理依赖版本兼容。
- 手动部署到外部 Tomcat。
- 每个项目重复配置数据源、MVC、JSON、事务、日志。
- 线上缺少统一健康检查和指标。
Spring Boot 把这些重复工作做成默认能力,让业务项目只关注业务差异。
为什么需要
商业项目中,一个订单服务、支付服务、采集服务、库存服务的基础能力高度相似:HTTP 接口、参数校验、数据库连接池、Redis、日志、监控、配置、多环境、优雅停机。如果每个项目都手写,容易出现三个问题:
| 问题 | 后果 |
|---|---|
| 配置重复 | 每个项目风格不同,出问题难排查 |
| 版本混乱 | 依赖冲突、运行时报错、升级困难 |
| 缺少默认治理 | 没有健康检查、指标、统一异常、配置排查入口 |
Spring Boot 的价值就是把“基础设施默认做好”,把“业务差异留给项目覆盖”。
不这样会怎样
如果没有 Spring Boot 或类似工程化框架,项目早期看起来灵活,后期会出现:
- 新服务创建慢,每次复制粘贴旧工程。
- XML、配置类、依赖版本散落各处。
- 线上启动失败时不知道哪个自动配置或 Bean 出错。
- 公共能力无法沉淀成 Starter,只能靠文档约定。
- 运维平台无法统一通过健康检查判断实例是否可接流量。
阶段 2:启动类和 SpringApplication.run
最小启动类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
}@SpringBootApplication 不是魔法,它主要组合了三个能力:
| 注解 | 作用 |
|---|---|
@SpringBootConfiguration | 表示这是一个 Spring 配置类 |
@ComponentScan | 扫描当前包及子包下的组件 |
@EnableAutoConfiguration | 开启 Spring Boot 自动配置 |
启动全过程
flowchart TD
A["main 方法"] --> B["创建 SpringApplication"]
B --> C["推断应用类型"]
C --> D["加载初始化器和监听器"]
D --> E["准备 Environment"]
E --> F["创建 ApplicationContext"]
F --> G["解析启动类和自动配置"]
G --> H["注册 BeanDefinition"]
H --> I["refresh 创建 Bean"]
I --> J["启动内嵌 Tomcat"]
J --> K["发布启动完成事件"]
K --> L["执行 Runner"]关键点:
Environment准备得很早,因为自动配置条件和配置绑定都要读取配置。- 自动配置先注册 BeanDefinition,不是马上创建所有对象。
- 真正创建非懒加载单例 Bean 主要发生在
refresh()阶段。 - Servlet Web 应用的内嵌 Tomcat 在
refresh()过程中创建和启动。 ApplicationRunner、CommandLineRunner在容器基本可用后执行。
常见误区
| 误区 | 正确理解 |
|---|---|
| 启动类一运行就创建所有 Bean | 先准备环境、解析配置类、注册定义,后面才创建 Bean |
| 自动配置等于直接 new 对象 | 自动配置本质是注册配置类和 BeanDefinition |
| Runner 能改自动配置条件 | Runner 太晚,条件判断和很多 Bean 创建已经结束 |
| 端口启动发生在最后 | 内嵌 WebServer 在 refresh() 中启动,Runner 之前通常已经可监听端口 |
详细原理继续看:启动流程全过程。
阶段 3:配置体系、Profile 和 Binder
配置加载解决什么问题
同一份代码要部署到开发、测试、预发、生产,数据库地址、Redis 地址、线程池大小、日志级别、医院接口地址都不同。配置体系的作用就是把环境差异从代码里抽出来。
配置来源
常见配置来源包括:
application.ymlapplication-dev.yml- 环境变量
- JVM 参数
- 命令行参数
- 配置中心
- 测试注解配置
线上排查时不能只看某一个 yml 文件,因为最终生效值可能被更高优先级的配置源覆盖。
配置绑定 Demo
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.validation.annotation.Validated;
@Validated
@ConfigurationProperties(prefix = "collect.client")
public class CollectClientProperties {
private boolean enabled = true;
private String endpoint;
private int connectTimeoutMillis = 3000;
private int readTimeoutMillis = 5000;
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public String getEndpoint() {
return endpoint;
}
public void setEndpoint(String endpoint) {
this.endpoint = endpoint;
}
public int getConnectTimeoutMillis() {
return connectTimeoutMillis;
}
public void setConnectTimeoutMillis(int connectTimeoutMillis) {
this.connectTimeoutMillis = connectTimeoutMillis;
}
public int getReadTimeoutMillis() {
return readTimeoutMillis;
}
public void setReadTimeoutMillis(int readTimeoutMillis) {
this.readTimeoutMillis = readTimeoutMillis;
}
}collect:
client:
enabled: true
endpoint: https://hospital.example.com/open-api
connect-timeout-millis: 3000
read-timeout-millis: 5000为什么推荐 @ConfigurationProperties,而不是到处写 @Value:
| 对比点 | @Value | @ConfigurationProperties |
|---|---|---|
| 适用场景 | 单个简单值 | 一组有前缀的配置 |
| 类型转换 | 可以但分散 | 统一绑定 |
| 嵌套结构 | 不方便 | 方便 |
| 校验 | 不直观 | 可配合校验注解 |
| 配置排查 | 分散 | 可通过 /actuator/configprops 查看 |
Binder 怎么工作
flowchart TD
A["配置源 PropertySource"] --> B["合并到 Environment"]
B --> C["根据 prefix 找配置项"]
C --> D["宽松名称匹配"]
D --> E["类型转换"]
E --> F["填充属性对象"]
F --> G["执行校验"]
G --> H["注册为 Spring Bean"]宽松名称匹配表示 connect-timeout-millis、connectTimeoutMillis、CONNECT_TIMEOUT_MILLIS 在不同来源下可以映射到同一个 Java 属性。这是为了兼容 yml、properties、环境变量等不同配置风格。
不这样会怎样
如果把医院接口地址、超时时间、线程池参数写死在代码里:
- 生产改配置必须重新发版。
- 测试和生产容易误连。
- 密钥可能被提交到代码仓库。
- 自动配置条件无法基于配置开关生效。
- 线上排查无法通过
/actuator/env、/actuator/configprops看到最终值。
详细原理继续看:配置体系全过程。
阶段 4:自动配置全过程
自动配置不是魔法
自动配置可以理解为一句话:Spring Boot 根据 classpath、配置项、已有 Bean、Web 类型等条件,决定要不要给你注册一组默认 Bean。
例如业务项目引入 spring-boot-starter-web 后,classpath 中出现 Spring MVC、Tomcat、Jackson 等类。Boot 发现这些类存在,就让 Web MVC、内嵌 Tomcat、JSON 转换等自动配置参与进来。
自动配置流程
flowchart TD
A["@SpringBootApplication"] --> B["@EnableAutoConfiguration"]
B --> C["导入 AutoConfigurationImportSelector"]
C --> D["读取自动配置候选类"]
D --> E["处理 exclude 和去重排序"]
E --> F["根据条件注解筛选"]
F --> G["注册 BeanDefinition"]
G --> H["refresh 阶段创建 Bean"]Boot 2 和 Boot 3 的自动配置声明方式要分清楚:
| 版本 | 常见声明文件 | 说明 |
|---|---|---|
| Spring Boot 2.6及更早 | META-INF/spring.factories | 通过 EnableAutoConfiguration key 声明自动配置类 |
| Spring Boot 2.7 | 两种机制可过渡 | 已支持并推荐 imports,同时兼容旧候选注册方式 |
| Spring Boot 3 | META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports | 使用每行一个自动配置类的新机制 |
面试时不要说“Boot 3 才有自动配置”。自动配置一直存在,只是候选类声明机制发生了变化。老项目使用 spring.factories 很常见,尤其是 Boot 2 生态的 Starter。
条件注解怎么理解
| 条件注解 | 含义 | 商业项目例子 |
|---|---|---|
@ConditionalOnClass | classpath 中存在某个类才生效 | 引入 HTTP 客户端 SDK 才装配采集客户端 |
@ConditionalOnMissingBean | 容器没有用户自定义 Bean 才创建默认 Bean | 默认提供客户端,业务可以覆盖 |
@ConditionalOnProperty | 配置开关满足才生效 | collect.client.enabled=true 才启用 |
@ConditionalOnBean | 已存在某个 Bean 才生效 | 有数据源时才创建数据访问组件 |
@ConditionalOnWebApplication | Web 应用才生效 | Web MVC 配置只在 Web 项目启用 |
默认 Bean Demo
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
@ConditionalOnClass(CollectClient.class)
@EnableConfigurationProperties(CollectClientProperties.class)
public class CollectClientAutoConfiguration {
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(prefix = "collect.client", name = "enabled", havingValue = "true", matchIfMissing = true)
public CollectClient collectClient(CollectClientProperties properties) {
return new CollectClient(
properties.getEndpoint(),
properties.getConnectTimeoutMillis(),
properties.getReadTimeoutMillis()
);
}
}业务项目如果不声明 CollectClient,自动配置就创建默认客户端。业务项目如果自己声明:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class BusinessClientConfig {
@Bean
public CollectClient collectClient() {
return new CollectClient("https://custom.example.com", 1000, 2000);
}
}因为默认配置上有 @ConditionalOnMissingBean,Boot 会让用户 Bean 优先。这就是“约定优于配置,但允许覆盖”的核心。
常见失败场景
| 现象 | 可能原因 | 排查入口 |
|---|---|---|
| 自动配置类完全没出现 | Starter 没引入,声明文件没打进 jar | 依赖树、jar 内容 |
| 出现在 Negative matches | 条件不满足 | debug=true、/actuator/conditions |
| Positive 但 Bean 没有 | 用户 Bean 覆盖、Bean 创建失败 | /actuator/beans、启动异常 |
| 配置项没生效 | Profile 错、被环境变量覆盖、绑定失败 | /actuator/env、/actuator/configprops |
详细原理继续看:自动配置原理 和 自动配置与扩展点全过程。
阶段 5:Starter 机制和自定义 Starter
Starter 是什么
Starter 是“场景依赖入口”。它本身通常不写复杂业务逻辑,而是聚合一组依赖,让业务项目引入一个依赖就获得某个场景的能力。
一个规范的自定义 Starter 通常拆成三层:
| 模块 | 职责 |
|---|---|
| SDK | 放纯 Java 客户端和核心能力,不依赖 Spring Boot |
| autoconfigure | 放自动配置类、属性类、条件注解、默认 Bean、健康检查、指标 |
| starter | 只做依赖聚合,把 SDK 和 autoconfigure 带进来 |
为什么要拆
如果把所有代码都塞到 starter 里,短期能用,长期会变成“大杂烩”:
- SDK 无法脱离 Spring Boot 复用。
- 自动配置逻辑无法单独测试。
- 业务代码和基础设施耦合。
- starter 引入后强制创建 Bean,用户无法覆盖。
商业 Demo:医疗采集客户端 Starter
假设多个业务服务都要调用医院采集接口,可以沉淀一个 collect-client-spring-boot-starter。
自动配置类:
@Configuration
@EnableConfigurationProperties(CollectClientProperties.class)
public class CollectClientAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public CollectClient collectClient(CollectClientProperties properties) {
return new CollectClient(properties.getEndpoint());
}
@Bean
public HealthIndicator collectClientHealthIndicator(CollectClient client) {
return () -> client.ping()
? Health.up().withDetail("collectClient", "reachable").build()
: Health.down().withDetail("collectClient", "unreachable").build();
}
}Boot 3 自动配置声明文件:
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.importscom.example.collect.CollectClientAutoConfiguration业务项目只需要:
collect:
client:
endpoint: https://hospital.example.com/open-api然后注入:
@Service
public class CollectTaskService {
private final CollectClient collectClient;
public CollectTaskService(CollectClient collectClient) {
this.collectClient = collectClient;
}
public void pullData(String hospitalCode) {
collectClient.pull(hospitalCode);
}
}这就是商业项目里 Starter 的常见价值:统一配置、统一默认实现、统一健康检查、统一指标、统一错误诊断。
详细原理继续看:Starter全过程。
阶段 6:内嵌 Tomcat 和一次 HTTP 请求链路
内嵌 Tomcat 为什么能启动
Spring Boot Web 项目启动时会创建 ServletWebServerApplicationContext。它在 refresh() 的 onRefresh() 阶段调用 createWebServer(),通过 ServletWebServerFactory 创建内嵌 Tomcat、Jetty 或 Undertow。
如果 classpath 中有 Tomcat,并且是 Servlet Web 应用,Boot 默认会配置 Tomcat。
请求链路
flowchart TD
A["HTTP 请求"] --> B["Tomcat Connector"]
B --> C["Servlet Filter 链"]
C --> D["DispatcherServlet"]
D --> E["HandlerMapping 找 Controller"]
E --> F["ArgumentResolver 参数解析"]
F --> G["参数校验"]
G --> H["Controller"]
H --> I["Service 业务编排"]
I --> J["Mapper 或 Repository"]
J --> K["数据库 Redis MQ 下游"]
K --> L["返回业务结果"]
L --> M["HttpMessageConverter 写 JSON"]Filter、Interceptor、ArgumentResolver 怎么选
| 扩展点 | 执行位置 | 适合场景 |
|---|---|---|
| Filter | Servlet 层,进入 Spring MVC 前 | traceId、编码、底层安全过滤 |
| Interceptor | Spring MVC 找到 Handler 后 | 登录态、权限、接口审计、访问日志 |
| ArgumentResolver | Controller 参数解析阶段 | 自动注入当前用户、租户、请求上下文 |
| ResponseBodyAdvice | 返回对象写出前 | 统一响应包装、补充 traceId |
| ControllerAdvice | Controller 异常处理 | 统一异常和错误码 |
不这样会怎样
如果每个 Controller 都手动解析 token、校验参数、try-catch 包异常:
- 代码重复。
- 不同接口返回结构不一致。
- 权限和审计容易漏。
- 出问题时 traceId、错误码、日志字段不统一。
所以商业项目通常把这些能力放进 Web 层扩展点,而不是散落到业务方法里。
阶段 7:扩展点选择
扩展点不是越早越好,也不是越多越好。关键是你要知道自己想干预哪一个阶段。
flowchart TD
A["配置还没完全准备"] --> B["EnvironmentPostProcessor"]
B --> C["Context 创建但未刷新"]
C --> D["ApplicationContextInitializer"]
D --> E["BeanDefinition 注册阶段"]
E --> F["BeanDefinitionRegistryPostProcessor"]
F --> G["修改 BeanDefinition"]
G --> H["BeanFactoryPostProcessor"]
H --> I["Bean 初始化前后增强"]
I --> J["BeanPostProcessor"]
J --> K["容器启动完成"]
K --> L["ApplicationRunner"]
L --> M["长期组件启动停止"]
M --> N["SmartLifecycle"]扩展点速查
| 扩展点 | 什么时候用 | 选错后果 |
|---|---|---|
EnvironmentPostProcessor | 配置解密、提前注入配置源 | 放到 Runner 就太晚,自动配置已经判断完 |
ApplicationContextInitializer | refresh() 前调整 Context | 不能访问完整业务 Bean |
BeanDefinitionRegistryPostProcessor | 批量注册 BeanDefinition | 提前 getBean() 会导致 Bean 过早创建 |
BeanFactoryPostProcessor | 修改 BeanDefinition 属性 | 不适合写业务逻辑 |
BeanPostProcessor | Bean 初始化前后增强 | 可能影响 AOP、代理、生命周期 |
ApplicationRunner | 启动后轻量预热和检查 | 放长任务会拖慢启动 |
SmartLifecycle | 管理长连接、消费者、采集器生命周期 | 构造方法启动线程会导致关闭不可控 |
HealthIndicator | 暴露健康检查 | 检查过重会拖慢探针 |
MeterBinder | 注册业务指标 | tag 基数过高会打爆监控系统 |
FailureAnalyzer | 启动失败给出友好诊断 | 没有诊断时开发只能看长堆栈 |
配置解密 Demo
配置解密必须发生得足够早,否则数据源、Redis、自动配置条件可能已经读取了未解密值。
import org.springframework.boot.SpringApplication;
import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MapPropertySource;
import java.util.HashMap;
import java.util.Map;
public class DecryptEnvironmentPostProcessor implements EnvironmentPostProcessor {
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
String encrypted = environment.getProperty("collect.client.secret");
if (encrypted == null || !encrypted.startsWith("ENC(")) {
return;
}
String plain = decrypt(encrypted);
Map<String, Object> values = new HashMap<>();
values.put("collect.client.secret", plain);
environment.getPropertySources().addFirst(new MapPropertySource("decryptedProperties", values));
}
private String decrypt(String encrypted) {
return encrypted.substring(4, encrypted.length() - 1);
}
}Boot 2 注册:
org.springframework.boot.env.EnvironmentPostProcessor=\
com.example.boot.DecryptEnvironmentPostProcessorBoot 3 仍可使用对应的 SPI 声明方式,实际项目要根据 Boot 版本查看官方支持的注册文件。重点不是背文件名,而是知道:配置解密必须在配置绑定和自动配置条件判断前完成。
健康检查 Demo
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.stereotype.Component;
@Component
public class CollectDependencyHealthIndicator implements HealthIndicator {
private final CollectClient collectClient;
public CollectDependencyHealthIndicator(CollectClient collectClient) {
this.collectClient = collectClient;
}
@Override
public Health health() {
if (collectClient.ping()) {
return Health.up().withDetail("dependency", "collect-api").build();
}
return Health.down().withDetail("dependency", "collect-api").build();
}
}生产上要注意:健康检查不能每次都访问一个很慢的外部接口,否则 K8s 或网关频繁探测会把外部系统打爆。常见做法是使用短超时、缓存最近检查结果、区分 liveness 和 readiness。
详细原理继续看:Spring Boot 扩展点。
阶段 8:Actuator、可观测性和生产排查
Actuator 是什么
Actuator 是 Spring Boot 面向生产的运维端点体系。它不是业务接口,而是给开发、运维、监控平台看的诊断入口。
常见端点:
| 端点 | 作用 |
|---|---|
/actuator/health | 健康检查 |
/actuator/metrics | 指标 |
/actuator/prometheus | Prometheus 抓取指标 |
/actuator/env | 查看最终环境配置 |
/actuator/configprops | 查看配置绑定结果 |
/actuator/conditions | 查看自动配置条件匹配 |
/actuator/beans | 查看 Bean |
/actuator/threaddump | 查看线程栈 |
/actuator/loggers | 动态查看或调整日志级别 |
生产排查总流程
flowchart TD
A["发现问题"] --> B["确认现象和影响范围"]
B --> C["查看日志和 traceId"]
C --> D["看 Actuator 指标和健康状态"]
D --> E["判断慢在应用还是下游"]
E --> F["检查线程池连接池和 GC"]
F --> G["检查 DB Redis MQ 外部接口"]
G --> H["定位根因并制定修复"]常见问题排查
| 问题 | 排查路径 |
|---|---|
| 启动失败 | 看异常根因、conditions、配置绑定、端口占用、Bean 创建失败 |
| 自动配置没生效 | 看 starter 依赖、自动配置声明、conditions、exclude、用户 Bean |
| 配置不生效 | 看 active profile、配置源优先级、env、configprops |
| 接口慢 | traceId、Controller、Service、慢 SQL、Redis、MQ、下游、线程栈 |
| 内存升高 | heapdump、GC 日志、对象引用、缓存、大对象、线程本地变量 |
| CPU 飙高 | top、线程栈、死循环、序列化、正则、频繁 GC |
| 健康检查失败 | 区分 liveness/readiness,检查依赖、连接池、超时策略 |
Actuator 安全边界
生产不能随便暴露所有端点。env、configprops、beans、heapdump、threaddump 可能泄露密钥、内部类结构、线程栈、内存内容。常见做法:
- 只对公网暴露必要的 health 或 info。
- 管理端口走内网。
- 敏感端点加鉴权。
- health 细节不要直接暴露给外部。
- 敏感配置做脱敏。
详细原理继续看:Actuator监控。
阶段 9:商业场景验收
场景 1:订单服务统一 Web 能力
目标:订单服务所有接口都需要统一 traceId、统一异常、统一返回、参数校验。
推荐设计:
| 能力 | 放置位置 |
|---|---|
| traceId | Filter |
| 登录用户 | Interceptor 或 ArgumentResolver |
| 参数校验 | Bean Validation |
| 统一异常 | @RestControllerAdvice |
| 统一返回 | ResponseBodyAdvice |
| 接口耗时 | Interceptor 或 AOP |
验收问题:
- 为什么 traceId 更适合 Filter?
- 为什么业务异常不应该在每个 Controller 里 try-catch?
- 文件下载接口为什么要排除统一响应包装?
场景 2:医疗采集客户端 Starter
目标:多个服务都要调用医院采集接口,不希望每个项目重复写客户端、配置、健康检查和指标。
推荐设计:
- SDK 提供
CollectClient。 - autoconfigure 提供
CollectClientAutoConfiguration。 @ConfigurationProperties承接 endpoint、timeout、token。@ConditionalOnMissingBean允许业务覆盖默认客户端。HealthIndicator暴露外部依赖状态。MeterBinder统计调用次数、耗时、失败率。FailureAnalyzer在缺少 endpoint 时给出友好提示。
验收问题:
- 为什么 starter 不应该直接写业务表逻辑?
- 为什么默认 Bean 要加
@ConditionalOnMissingBean? - 为什么健康检查要区分存活和就绪?
场景 3:生产接口突然变慢
排查顺序:
- 通过网关日志或应用日志拿到 traceId。
- 看接口耗时分布,是所有接口慢还是单接口慢。
- 看 Actuator metrics:Tomcat 线程、连接池、JVM、HTTP server requests。
- 看数据库慢 SQL、锁等待、连接池等待。
- 看 Redis timeout、MQ 堆积、外部 HTTP 超时。
- 看线程栈是否大量阻塞在同一个资源。
- 看 GC 日志和内存是否异常。
不要只说“加缓存”。如果根因是连接池耗尽、下游超时、线程池队列堆积,加缓存可能掩盖问题甚至扩大故障。
阶段 10:面试闭环
面试页负责标准回答,本页负责原理。学习时按下面方式闭环:
flowchart TD
A["先看面试题"] --> B["知道面试问什么"]
B --> C["回到知识点页理解原理"]
C --> D["写 Demo 验证"]
D --> E["补商业场景和排查话术"]
E --> F["回面试页组织回答"]高频问题必须能答到这个深度:
| 面试题 | 合格回答深度 |
|---|---|
| Spring Boot 自动配置原理 | 说清 @EnableAutoConfiguration、候选类、Boot 2/3 声明差异、条件注解、BeanDefinition、IOC 创建 |
| Starter 为什么开箱即用 | 说清 starter、autoconfigure、SDK 边界,以及 @ConditionalOnMissingBean 让用户覆盖 |
| 配置为什么能绑定对象 | 说清 PropertySource、Environment、Binder、宽松绑定、类型转换、校验、Actuator 排查 |
| 扩展点怎么选 | 说清生命周期阶段,配置解密、BeanDefinition 注册、Bean 增强、启动后预热、健康检查分别用什么 |
| 启动慢怎么排查 | 说清配置中心、Bean 创建、外部连接、Runner、端口、线程栈、Actuator startup |
| 接口慢怎么排查 | 说清 traceId、指标、线程池、连接池、DB、Redis、MQ、外部接口、GC |
面试标准回答看:Spring Boot 面试题。
最终验收题
你可以用下面的问题检查自己是否真正学会:
@SpringBootApplication由哪些核心注解组成?每个注解解决什么问题?- 从
SpringApplication.run到端口监听成功,中间有哪些关键阶段? - 自动配置类是怎么被发现的?Boot 2 和 Boot 3 声明方式有什么区别?
@ConditionalOnClass、@ConditionalOnMissingBean、@ConditionalOnProperty分别适合什么场景?- 为什么
@ConditionalOnMissingBean要尊重用户自定义 Bean? - 为什么配置解密不能放在
ApplicationRunner? @Value和@ConfigurationProperties怎么选?- 一个 Starter 为什么最好拆成 SDK、autoconfigure、starter 三层?
- 内嵌 Tomcat 什么时候创建?一次请求如何进入 Controller?
- Filter、Interceptor、ArgumentResolver、ResponseBodyAdvice 分别解决什么问题?
- Actuator 的
/conditions、/env、/configprops分别用于排查什么? - Liveness 和 Readiness 为什么不能混用?
- 生产接口慢时,为什么不能第一反应只说“加缓存”?
- 自定义 HealthIndicator 有什么风险?如何避免探针把依赖打爆?
- 如何把医疗采集客户端做成一个可复用 Starter?
本章小结
Spring Boot 的主线不是“少写配置”,而是“用一套可解释、可覆盖、可排查的默认机制把应用工程化”。真正掌握 Spring Boot,要把启动流程、配置体系、自动配置、Starter、内嵌 WebServer、请求链路、扩展点和 Actuator 串起来。这样你不仅会用,还能解释为什么这样设计;线上出问题时,也知道从哪一层开始拆。
