实测 WebClient:默认共享连接池最多 500 个连接,自建 ConnectionProvider 不写上限只有 20;池满后请求排队,pendingAcquireTimeout 到期抛 PoolAcquireTimeoutException;默认没有响应超时(等到 2149 ms),要用 Mono.timeout 或 spring.http.clients.read-timeout;Retry.backoff 不加 filter 会把 404 也重试(服务端被打 4 次);非幂等 POST 加 timeout 重试会让服务端写入 3 次;retrieve() 对 4xx/5xx 抛异常,空响应体要 defaultIfEmpty。
同一份「每个请求调下游 100 ms」的负载,在 Spring Boot 4.0.8 上实测:MVC 默认 200 个平台线程时,并发 1000 只有约 2000 请求/秒(p50 550 ms);打开 spring.threads.virtual.enabled 后代码不改,并发 1000 达到约 9100 请求/秒(p50 约 110 ms),但 RSS 从 439 MB 涨到 688 MB;WebFlux 在并发 1000 时吞吐与虚拟线程版同量级(约 9100 请求/秒),RSS 只有 375 MB。并发 3000 时压测机和被测应用挤在同一台机器上,结果波动很大。
在本机 PostgreSQL 16 上实测 Spring Data R2DBC:repo.save 不订阅就不发 INSERT;没有事务时同一方法里的两条语句可能落在不同连接上(pid 不同),@Transactional 里则是同一个连接;失败回滚只在事务里生效,链外的 subscribe() 或 boundedElastic 上的 block() 写入的行不会回滚;默认连接池服务端最多同时 10 个连接,池满排队,max-acquire-time 到期抛 R2dbcTimeoutException。
实测响应式下的上下文传播:ThreadLocal 一旦经过 publishOn、delay、subscribeOn 就读到 null;Reactor Context 随订阅从订阅者向源头传,contextWrite 只影响上游,flatMap 内层继承而手动 subscribe() 的新链读不到,cache() 会把第一个订阅者的值带给后面的订阅者;注册 ThreadLocalAccessor 并开启 Hooks.enableAutomaticContextPropagation() 才能让 ThreadLocal 跟着走,缺一个都读到 null。
实测 Spring 的 WebSocket 与 STOMP:默认单条文本消息上限正好是 8192 字节,多 1 字节连接以 1009 断开,要自己调大;多个线程同时向同一个 session 发消息会抛 IllegalStateException(TEXT_PARTIAL_WRITING),用 ConcurrentWebSocketSessionDecorator 才安全;STOMP 的 @MessageMapping + @SendTo 广播,客户端也能绕过应用直接往 /topic 广播;简单代理默认不开心跳(heart-beat:0,0);多实例之间简单代理互不相通。
实测 Reactor 的错误处理:错误是终止信号,onErrorReturn / onErrorResume 能接着发,onErrorContinue 依赖操作符;恢复写在 flatMap 内层只替换出错元素,写在外层则整个流结束;retry 重新订阅整条上游,Flux 会重复发出前面的元素,Retry.backoff 耗尽抛 RetryExhaustedException;Flux.timeout 限制的是元素间隔不是总时长,超时会取消上游并中断阻塞的任务;取消之后才到的错误大多无声消失,违反协议的源才进 onErrorDropped。
实测 Spring Boot 4 自动配置:AutoConfiguration.imports 里列出的类才会被导入,没列的 @AutoConfiguration 类也不会被组件扫描(AutoConfigurationExcludeFilter 挡掉);@AutoConfiguration(after/before) 决定评估顺序,@ConditionalOnBean 在 before 的配置里因 Bean 还没注册而失败;用户的 Bean 在先时 @ConditionalOnMissingBean 让路,同时让 before 的配置匹配;条件报告能看到每个条件的原因;spring.autoconfigure.exclude 按类名排除。
实测 Spring Boot 4 的 starter 到底是什么:starter 的 jar 里 0 个 .class,只有一个 pom;webmvc 的 pom 直接依赖 5 个;各个 starter 拉来的 jar 数差别很大(核心 21 个、webmvc 39 个、webflux 65 个、data-jpa 55 个);同一份启动代码,类路径从 starter 换成 starter-webmvc,Bean 数从 50 变成 143,应用类型从 NONE 变成 SERVLET。
自己写一个库并实测:spring-boot-configuration-processor 在编译时生成 spring-configuration-metadata.json(说明文字取自字段 Javadoc);库放进 classpath 零配置生效,greeting.* 属性覆盖默认值,用户自己的 Bean 让路;漏写 AutoConfiguration.imports 时自动配置静默失效没有报错;可选依赖要用类级别 @ConditionalOnClass 单独保护,把 @ConditionalOnClass 写在返回类型就是可选类的方法上,缺依赖时启动失败(ClassNotFoundException)。
实测 Spring Boot 4 的条件报告:--debug、-Ddebug=true 或只把 logging.level.org.springframework.boot.autoconfigure.logging 设成 DEBUG 都能打印 CONDITIONS EVALUATION REPORT;本机 webmvc 非 Web 模式下 Positive matches 23 项、Negative matches 37 项;每一项带条件原文(not a servlet web application、did not find bundle with basename messages 等);用户定义 JsonMapper 后,jacksonJsonMapper 从 Positive 变成 Negative,原因直接点名 myMapper。
实测 Spring Boot 4 外部化配置的优先级:同一个 app.name 放进 12 个来源,逐个拿掉最高的,顺序是命令行参数 > SPRING_APPLICATION_JSON > JVM 系统属性 > 操作系统环境变量 > jar 外 ./config > jar 外 ./ > jar 内 /config > jar 内根 > @PropertySource > setDefaultProperties;jar 内的 profile 专属文件输给 jar 外的非专属文件;Environment.getProperty 对 key 的写法要对上规范形式;占位符支持嵌套与默认值。
实测超时预算:每一跳固定 1000 ms 时 C 为 74 个已放弃的请求白干;传播剩余预算后 C 干活时间从 123.6 s 降到 73.9 s;重试 3 次会吃掉预算(120 次对 80 次调用);共用剩余预算的成功率 98.2% 对平分的 87.5%;排队超过截止时间就丢,成功数 34 → 162。