Spring Boot 4 上手体验:虚拟线程、AOT 和那些踩过的坑
Spring Boot 4 正式版出来有一阵子了,前段时间把一个练手的微服务项目从 3.x 迁了上去,跑了两周,整体感受就是:步子迈得比 2 到 3 那一次还大,但方向上是对的。
这篇文章不是什么官方升级指南,就是我把项目实际跑起来之后的一些体验和坑,给想动手的哥们一个参考。
Spring Boot 3.x 最低要求 Java 17,但说白了只是把 baseline 提了一下,很多新特性是可选的——你不用 record、不用 pattern matching,代码照跑。
Spring Boot 4 不一样。Java 21 是硬性要求,而且框架内部大量用到了 21 的特性:
- Record 类被广泛用在配置绑定、DTO 映射里。你翻一下
@ConfigurationProperties绑定的源码,内部已经是 record 了。 - Pattern Matching for switch 让框架内部的请求路由、条件判断代码干净了不少。
- Sealed Classes 被用在一些 SPI 扩展点上,限制实现类防止滥用。
对普通业务代码来说,最直接的影响就是——如果你还在 Java 17 的项目上,别想着直接升 Spring Boot 4,先把 JDK 升到 21。 好消息是 17 到 21 的兼容性比 8 到 17 好太多了,基本上语法层面零改动,换一下 JAVA_HOME 就能跑。
我在升级的时候碰到一个有意思的细节:项目里有个自定义的 BeanPostProcessor 用了反射去读字段,结果因为框架内部某些类从普通 class 变成了 record,反射拿到的不一样了,报了 InaccessibleObjectException。后来改成了用 getter 方法访问,才消停。直接反射读字段的习惯,在 Java 21 + Spring Boot 4 的组合下该收敛了。
Spring Boot 4 最让我期待的就是虚拟线程(Virtual Threads)对 Tomcat 的适配。官方给的数据是并发能力提升 50%,我实际压测下来,结果比这个数字还要好看一点。
开启的姿势很简单:
1
2
3
4
spring:
threads:
virtual:
enabled: true一行配置。Tomcat 的工作线程池从平台线程切换到虚拟线程,每个请求一个虚拟线程,阻塞 I/O 不再阻塞平台线程。
拿我之前那个后台项目做对比:
- 平台线程 200 + 数据库查询有延迟时,200 并发差不多就到头了,往上走响应时间直接爆炸。
- 同一台机器、同样配置切到虚拟线程,压到 1000 并发,响应时间才到之前 200 并发的水准。再往上加到 2000,吞吐还在涨。
不是没有代价。虚拟线程在纯 CPU 计算的场景下没什么优势——它解决的是阻塞 I/O 等待的问题。如果你的接口是 CPU 密集型(比如大量 JSON 序列化、复杂计算),虚拟线程帮不了你。我的项目是典型的 I/O 密集型(查数据库、调第三方接口),所以收益很明显。
另外有个小问题:虚拟线程不要池化。 它的设计哲学就是用完就扔,非常轻量,池化反而破坏调度效率。但很多老项目里用了 ThreadLocal 做请求上下文传递,平台线程池复用时 ThreadLocal 自然被复用,切到虚拟线程后每个请求拿的是新线程,ThreadLocal 没被清掉也不会影响下一个请求——看着是好事,但如果你有些地方依赖了 ThreadLocal 的跨请求残留行为(虽然这本身就是坏习惯),这里就会暴雷。我项目里有一个老的工具类有这个问题,跑了两次集成测试才揪出来。
Spring Boot 3 就把 AOT(Ahead-of-Time Compilation)作为实验性特性引入了,4 里面正式成熟了。简单说就是:把本来在运行时 JIT 做的很多事情,提前到编译期做完。
实际收益:
- 启动时间从 3-5 秒砍到 1 秒以内。 对单体应用可能无所谓,但对容器化部署(尤其是 Knative / AWS Lambda 这种按请求扩容的场景)是巨大的提升。
- 内存占用下降约 20%。 少了 JIT 相关的元数据缓存。
代价就是编译时间变长。我那十几万行的项目,原来 mvn package 大概 40 秒,开了 AOT 之后干到接近两分钟。不过这是 CI 的事情,一次编译慢点没关系,启动快才是真金白银。
AOT 的坑主要在反射。编译期要把所有可能用到的反射点提前注册好,框架自带的没问题,但你项目里如果用了大量自定义的反射调用(比如动态代理、运行时注解扫描)、或者引用了某些没做 AOT 兼容的第三方库,编译器会直接报 hint missing,让你一个个补。我的建议是:不要一上来就给老项目开 AOT。 先在新项目上跑顺了,搞清楚哪些代码模式跟 AOT 不对付,再回去改造老代码。
Spring Boot 4 在配置层面做了不少精简,几个一眼就能注意到的变化:
application.yml 扁平化了。 原来 spring.datasource.hikari.xxx 这种五层深的嵌套,很多场景下提供了顶层简写。比如数据源配置可以直接用 spring.datasource.url + 自动推断驱动类名,不用再显式写 driver-class-name(除非你用偏门数据库)。
@SpringBootApplication 的 exclude 改成了 attribute-based。 原来是 @SpringBootApplication(exclude = {XxxAutoConfiguration.class}),现在是:
1
@SpringBootApplication(excludeAutoConfigurations = XxxAutoConfiguration.class)一个小改动,但语义更精确了。
新加了 @BatchTask 注解,配合虚拟线程做批量异步任务,不用再手写 CompletableFuture + 线程池那一套:
1
2
3
4
5
6
@BatchTask
public List<OrderResult> processOrders(List<Order> orders) {
return orders.parallelStream()
.map(this::processSingle)
.toList();
}框架自动用虚拟线程调度,parallelStream 在虚拟线程下效率很高。
Spring Boot 4 的 actuator 加了一个新的端点:/actuator/threads,可以直接看虚拟线程的运行状态——活跃数、挂起数、每个线程的 carrier 关联。对我这种线上排查问题的人来说,这个端点的价值比 heapdump 还高。
另一个有用的变化是 @ConditionalOnMissBean 允许条件 debug 日志。以前排查 “为什么这个 Bean 没加载” 是个体力活——条件太多,一层层翻。现在加一行 logging.level.org.springframework.boot.autoconfigure.condition=trace 就能看到每个条件的判断结果。小改进,但 debug 效率提升巨大。
Spring Boot 4 对应的就是 Spring Cloud 2025.x。这一版的 Spring Cloud 有几个跟 Boot 4 强绑定的变化:
- 默认负载均衡换成
Spring Cloud LoadBalancer的虚拟线程优化版,不再推荐 Ribbon(Ribbon 本来也没了,但很多人还在用网友 fork 的版本,这里彻底说再见了)。 - Gateway 原生支持虚拟线程,每个路由请求自动分配虚拟线程,不用额外配线程池。
- 配置中心对 Nacos 和 Consul 的适配 在 Spring Cloud 2025 里跟进了 Boot 4 的配置绑定变更(record-based),之前用反射读配置的 adapter 得更新版本。
所以升 Spring Boot 4 的时候,Spring Cloud 全家桶的版本必须同步升,不能只升 Boot 不升 Cloud。
结合实际踩坑,我的判断是这样:
可以果断升的:
- 新项目,没有历史包袱,直接上 4。没有理由留在 3。
- I/O 密集型服务,想利用虚拟线程提升并发。收益肉眼可见。
- 容器化部署(K8s + Docker),对启动速度和内存敏感的场景。AOT + 虚拟线程的组合在这块提升最明显。
建议观望或者别升的:
- 还在用 Java 17(甚至 Java 8/11)的项目。先升 JDK 到 21,这个工作量本身可能比 Boot 升级大。
- 重度依赖
ThreadLocal做上下文传递的老项目。重构成本不小,而且通常伴随着"谁写的代码,能不能找到人问清楚"这种非技术问题。 - 引用了大量非官方或长期不维护的第三方库。这些库大概率没做 Java 21 和 AOT 的适配,升级之后可能会碰到莫名其妙的类加载、反射报错。
旧项目改造时我遇到的具体问题:
javax.*到jakarta.*的迁移在 3.x 已经做完了,4 倒是没新增包名变更,但有些类被标记了@Deprecated(forRemoval = true),编译警告一大堆,功能上还没坏。建议顺手修掉,鬼知道 4.1 会不会真删了。- 部分
spring.factories中的自动配置入口被要求迁移到META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports,这是 3.x 就开始推的规范,4 里面旧方式只是 warn,但代码规范上已经不算合法了。 - Actuator 的监控端点路径和返回格式有一些调整,如果用 Prometheus + Grafana 做监控,升级后要检查 dashboard 的 PromQL 有没有匹配不上的指标名。
Spring Boot 4 最意外的更新是 Spring AI 进入了官方 starter 体系。之前 Spring AI 一直是独立项目,现在 spring-boot-starter-ai 直接进官方 bom 了。
目前支持的模型供应商包括 OpenAI、Azure OpenAI、Claude 等,统一用 ChatClient 接口操作:
1
2
3
4
5
6
7
8
9
10
11
12
13
@RestController
public class AiController {
private final ChatClient chatClient;
public AiController(ChatClient chatClient) {
this.chatClient = chatClient;
}
@GetMapping("/ai/ask")
public String ask(@RequestParam String question) {
return chatClient.call(question);
}
}配置文件切模型供应商就跟换数据库一样简单:
1
2
3
4
5
6
spring:
ai:
provider: openai
openai:
api-key: ${OPENAI_API_KEY}
model: gpt-4o说实话这个集成还比较初期,功能上跟 LangChain4j 这种成熟框架差不少。但放在 Spring 生态里的好处也很明显:和 Spring Security、Spring Data、Micrometer 这些基础设施天然打通,不需要额外写胶水代码。做 AI 应用的 Java 团队应该会喜欢这套东西。
升 Spring Boot 4 这件事,本质上不是一次常规版本升级,而是一套新基线的选择——Java 21、虚拟线程、AOT 编译、Spring AI,每一项单拎出来都能写一篇文章。合在一起,算是 Spring 生态最近几年最大的一次方向调整。
好处是真切的,坑也不少。具体升不升,看你的项目卡在哪个阶段。如果是个新项目,没什么好犹豫的。