知其然,知其所以然
Spring 全家桶 2026-10-20

启动时间分析:时间花在哪里,Lazy 与 AppCDS

实测一个 409 个 Bean 定义的 Spring Boot 4 应用的启动时间:refresh 阶段占 main 到 Ready 的大头,最慢的 Bean 都是框架自己的;spring.main.lazy-initialization 让启动快 166 ms 但第 1 次请求从 41.7 ms 变成 123.0 ms;-XX:TieredStopAtLevel=1 略快,SerialGC 不快;AppCDS 动态归档把外部计时启动时间从 1083 ms 降到 759 ms。

Spring Boot 启动时间 懒加载 AppCDS 真实验证 动画演示
Spring 全家桶 2026-10-20

内嵌容器:Tomcat、Jetty、Undertow 的差别

同一份 Spring Boot 4.0.8 webmvc 应用换内嵌容器:Tomcat 与 Jetty 的启动、内存和快接口吞吐相差不大;两个 starter 同时在 classpath 时跑的是 Jetty;阻塞接口在默认 200 线程下 Tomcat 与 Jetty 都约 3600 请求/秒,打开虚拟线程后 Tomcat 到 6467 而 Jetty 要再调大 threads.max;请求头超限、// 与 %2e%2e 的路径、空闲连接超时两个容器反应不同;Boot 4.0.8 已经没有 Undertow。

Spring Boot Tomcat Jetty Undertow 内嵌容器 真实验证 动画演示
Spring 全家桶 2026-10-20

优雅停机,以及和 K8s preStop 的配合

实测 Spring Boot 4.0.8 的优雅停机:server.shutdown 的默认值就是 graceful,在途的 3 秒请求能完成,新连接立刻被拒绝;graceful 救不了新请求,负载均衡摘除延迟 2 秒的模拟里,不睡 149~162 个请求全部失败,preStop 睡 3 秒加上 graceful 才是零失败;阶段超时默认等 30 秒。

Spring Boot 优雅停机 graceful shutdown preStop K8s 真实验证 动画演示
Spring 全家桶 2026-10-20

可执行 jar 的结构与分层 jar

实测 Spring Boot 4.0.8 可执行 jar 的结构:Main-Class 是 JarLauncher、Start-Class 才是自己的主类,34 个嵌套 jar 以不压缩方式存放;胖 jar 比解压后的目录启动慢 150 到 190 ms;分层提取之后改一个类只有 5 KB 的 application 层指纹变化,19192 KB 的 dependencies 层不变;在胖 jar 里 ClassPathResource.getFile() 会失败而 getInputStream() 正常。

Spring Boot 可执行jar 分层jar JarLauncher Docker 真实验证 动画演示
Spring 全家桶 2026-10-20

Actuator 端点:暴露、安全与开销

实测 Spring Boot 4.0.8 的 Actuator:默认只暴露 health 且不带明细;include=* 之后 19 个端点都没有认证;heapdump 和 shutdown 还要 access=unrestricted 才能用,开了之后 32.2 MB 的堆转储无需认证即可下载;/actuator/env 默认把所有值都脱敏;管理端口比业务端口晚开,刚开时 health 是 503;装 actuator 增加启动时间与内存。

Spring Boot Actuator 端点 安全 真实验证 动画演示
Spring 全家桶 2026-10-20

健康检查:health 组、liveness 与 readiness 探针

实测 Spring Boot 4.0.8 的健康检查:liveness 和 readiness 探针默认就是开的(不需要在 K8s 里);默认探针组只包含各自的 State,业务检查 DOWN 时探针仍是 200;UNKNOWN 与自定义状态不会改变整体状态;AvailabilityChangeEvent 可以让 readiness 变 503;每次请求都会重跑检查,慢检查串行叠加,cache.time-to-live 可以缓存。

Spring Boot 健康检查 liveness readiness 探针 真实验证 动画演示
Spring 全家桶 2026-10-20

Micrometer 指标:自带的与自定义的

实测 Spring Boot 4.0.8 的 Micrometer:自带 53 个指标名(含我自己的 2 个);http.server.requests 用路由模板做 uri 标签,乱扫的 404 全落在 uri=/**;Gauge 只弱引用被观察对象,GC 之后变 NaN;10 万个不同标签的 Counter 多占约 53 MB,用 MeterFilter.maximumAllowableTags 可以截在上限;Prometheus 抓取输出全部 Meter,Counter orders.created 变成 orders_total。

Spring Boot Micrometer 指标 Prometheus 基数 真实验证 动画演示
Spring 全家桶 2026-10-20

日志:Logback 配置、分组、滚动与运行时改级别

实测 Spring Boot 4.0.8 的日志:默认 INFO 启动 9 行、root=DEBUG 是 1064 行;结构化日志有 ecs、logstash、gelf 三种;logging.group 的设置盖过单独写的 logging.level;max-history 按天算,同一天内的归档不会被它删掉;可以通过 /actuator/loggers 在运行时改级别,包括分组;logback.xml 里的 springProfile 在这个版本同样生效。

Spring Boot 日志 Logback logging.group 滚动 真实验证 动画演示
Spring 全家桶 2026-10-20

测试:@SpringBootTest、切片测试与上下文缓存

实测 Spring Boot 4.0.8 的测试:@WebMvcTest 的上下文 107 个 Bean 定义、@SpringBootTest 154 个,在这个小应用上耗时差别不大;上下文按配置缓存,@MockitoBean、@TestPropertySource、@ActiveProfiles 任何一个不同都会新建上下文,@DirtiesContext 每个方法重建一次,8 个类 9 个方法只创建了 7 个上下文;@WebMvcTest 里没 mock 掉 Service 会因为 No qualifying bean 启动失败。

Spring Boot 测试 SpringBootTest WebMvcTest 上下文缓存 真实验证 动画演示
Spring 全家桶 2026-10-20

Testcontainers:用真实的 MySQL 和 Redis 做集成测试

实测 Spring Boot 4.0.8 加 Testcontainers 2.0.5 用真实 MySQL 和 Redis 做集成测试:@Container 加 @ServiceConnection 不用写连接配置,端口随机映射,测试结束容器被清掉;MySQL 里 where name = 'ABC' 命中 abc 而 H2 命中 0 行,ON DUPLICATE KEY UPDATE 在 H2 里报语法错误;两个测试类共用 @Bean 容器 7476 ms,各自一个容器 16949 ms。

Spring Boot Testcontainers MySQL Redis 集成测试 真实验证 动画演示
Spring 全家桶 2026-10-20

GraalVM Native Image 与 AOT:启动与内存的代价

实测 Spring Boot 4.0.8 加 GraalVM 25 原生镜像:构建 102 秒、峰值内存约 2737 MB、产物 74.3 MB;启动从 1023 ms 降到 28 ms,常驻内存 196 MB 降到 73 MB;没有反射提示的类会 ClassNotFoundException,运行时激活的 @Profile 不生效;稳定之后吞吐比 JVM 低约 14%。

Spring Boot GraalVM Native Image AOT 启动 真实验证 动画演示
Spring 全家桶 2026-10-20

FailureAnalyzer:启动失败的报错是怎么生成的

实测 Spring Boot 4.0.8 的启动失败报告:缺 Bean、两个 Bean、循环依赖、绑定失败、数据源、端口占用都有 Description 加 Action 的报告,占位符没有值却只有堆栈;classpath 上登记了 25 个 FailureAnalyzer;自己写一个并登记在 spring.factories 里就能得到自定义报告;--debug 之后控制台从 18 行变成 404 行。

Spring Boot FailureAnalyzer 启动失败 诊断 真实验证 动画演示
京ICP备2026067502号-1京公网安备11010502063487号