实测 Spring Boot 4.0.8 的 Actuator:默认只暴露 health 且不带明细;include=* 之后 19 个端点都没有认证;heapdump 和 shutdown 还要 access=unrestricted 才能用,开了之后 32.2 MB 的堆转储无需认证即可下载;/actuator/env 默认把所有值都脱敏;管理端口比业务端口晚开,刚开时 health 是 503;装 actuator 增加启动时间与内存。
实测 Spring Boot 4.0.8 的健康检查:liveness 和 readiness 探针默认就是开的(不需要在 K8s 里);默认探针组只包含各自的 State,业务检查 DOWN 时探针仍是 200;UNKNOWN 与自定义状态不会改变整体状态;AvailabilityChangeEvent 可以让 readiness 变 503;每次请求都会重跑检查,慢检查串行叠加,cache.time-to-live 可以缓存。
实测 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 4.0.8 的日志:默认 INFO 启动 9 行、root=DEBUG 是 1064 行;结构化日志有 ecs、logstash、gelf 三种;logging.group 的设置盖过单独写的 logging.level;max-history 按天算,同一天内的归档不会被它删掉;可以通过 /actuator/loggers 在运行时改级别,包括分组;logback.xml 里的 springProfile 在这个版本同样生效。
实测 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 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 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 4.0.8 的启动失败报告:缺 Bean、两个 Bean、循环依赖、绑定失败、数据源、端口占用都有 Description 加 Action 的报告,占位符没有值却只有堆栈;classpath 上登记了 25 个 FailureAnalyzer;自己写一个并登记在 spring.factories 里就能得到自定义报告;--debug 之后控制台从 18 行变成 404 行。
实测 Spring Boot 4.0.8 的 Observation API:一次 HTTP 请求同时产生指标和链路,日志自动带 traceId;低基数标签进指标也进 span,高基数标签只进 span;@Observed 一个注解同时得到 Timer 指标和 span;默认采样率下 100 个请求只保留 10 个 span,指标不受采样影响;按名字关闭观察会让指标和 span 一起消失。
实测 Spring Data JPA 的 Repository 接口为什么不用写实现:容器里是 JDK 动态代理 $Proxy107,Bean 定义是 JpaRepositoryFactoryBean,目标对象是 SimpleJpaRepository,代理上挂了 7 个拦截器;findAll/findById/count 在只读事务里,save 在可写事务里,派生查询与自定义片段没有事务。
实测 Spring Data JPA 派生查询的方法名到 SQL 的转换:And/Or/Between/In/Top/First/Exists/Count 各自对应的 SQL;Containing 和 StartingWith 会转义 %;IgnoreCase 变成 upper() 比较;deleteBy 先 select 再逐行 delete(3 行就是 1 + 3 条 SQL);List<String> 的派生查询会转换失败;方法名写错时启动就失败。
实测 Spring Data JPA 的 @Query 与分页:Page 是数据加 count 两条 SQL,Slice 和 List 只有一条;内容不足一页时可以省掉 count;原生查询的 Sort 要用列名;排序不唯一翻 50 页只读到 896 个不同行;offset 499980 的 Slice 要 91.7 ms 而 keyset 一直 0.7 ms;join fetch 加分页在内存里分页且 count 数的是 join 行数。