Spring 全栈硬核教程 · 07:前沿与扩展(Spring AI / 虚拟线程 / GraalVM / Modulith / Batch)
这一篇回答一个很现实的问题:2026 年,一个 Java 工程师该把有限的学习时间投在哪里?
前沿技术的价值不均等——有些会改变你写代码的方式(虚拟线程),有些只在特定场景值得(GraalVM),有些正在重塑一整个岗位的能力边界(Spring AI)。这一篇会诚实地告诉你每一项的真实收益、真实代价和适用边界,而不是无脑吹新。
1. ⭐ 虚拟线程:这一代最值得学的特性
1.1 它解决了什么
传统模型:一个请求 = 一个操作系统线程。线程贵(每个约 1MB 栈内存),创建慢,上下文切换成本高,所以只能用线程池复用。线程池大小一旦设定,就成了并发上限——阻塞在 I/O 上的线程,白白占着这个宝贵名额什么也不干。
虚拟线程:JVM 层面的轻量级线程,M:N 挂载到少量"载体线程"上。阻塞时,虚拟线程会被从载体线程上卸下,载体线程立刻去跑别的虚拟线程。于是"阻塞"的代价从"占死一个 OS 线程"降到"几乎为零"。
spring:
threads:
virtual:
enabled: true # 一行配置,Tomcat 用虚拟线程处理每个请求1.2 虚拟线程 vs Goroutine:调度模型硬核对比
| 维度 | Java 虚拟线程 | Go Goroutine |
|---|---|---|
| 调度 | JVM 的 ForkJoinPool 做 M:N 调度 | Go runtime 的 GMP 模型,M:N 调度 |
| 栈 | 堆上分配,按需增长 | 栈从 2KB 起,动态扩缩 |
| 编程模型 | ⭐ 完全兼容传统同步阻塞写法,存量代码零改造 | 语言原生并发原语,go func() + channel |
| 阻塞识别 | JVM 拦截 Thread.sleep、Socket I/O 等并自动卸载 | runtime 拦截系统调用,必要时扩容 M |
| 同步原语 | ReentrantLock 友好;synchronized 可能导致 Pinning | channel / mutex,无此问题 |
| 抢占 | 依赖 JVM 的安全点 | 基于信号的异步抢占(1.14+) |
| 最大优势 | 零改造升级存量 Java 代码 | 从语言层设计,心智负担最低 |
对 Java 生态的真正意义:Go 的并发优势是"重写一切换语言"换来的;Java 的虚拟线程是"改一行配置"换来的。对手握几十万行存量 Spring 代码的团队,这个差别是决定性的。
1.3 三个必须知道的陷阱
① Pinning(钉住)
// ❌ synchronized 块内做阻塞 I/O,虚拟线程会被"钉"在载体线程上,无法卸载
public synchronized void badMethod() {
httpClient.send(request); // 阻塞期间载体线程被白白占用
}
// ✅ 改用 ReentrantLock,虚拟线程可正常卸载
private final ReentrantLock lock = new ReentrantLock();
public void goodMethod() {
lock.lock();
try { httpClient.send(request); } finally { lock.unlock(); }
}Java 21 之后的版本在持续改善 Pinning 问题,但第三方库里的 synchronized 你改不动。排查手段:加 JVM 参数 -Djdk.tracePinnedThreads=full,会打印出被钉住的堆栈。
② ThreadLocal 内存放大
虚拟线程可以轻松创建几十万个,如果每个都挂一份 ThreadLocal 数据(MDC、SecurityContext、租户上下文),堆内存会爆炸。虚拟线程时代应该优先用 ScopedValue(Java 21 预览、后续版本转正),它是不可变的、作用域明确的替代方案。
③ 瓶颈会转移,不会消失
虚拟线程解除了"线程数"这个瓶颈,于是瓶颈转移到下一个环节——通常是数据库连接池。你的应用现在能同时处理 10000 个请求了,但连接池只有 20 个连接,9980 个请求在排队等连接。
实战结论:开启虚拟线程后,必须重新压测并重新评估连接池、下游服务容量、限流阈值。不然你只是把排队的地方从线程池挪到了连接池,还顺便丢掉了线程池自带的限流保护效果。
2. Spring AI:Java 后端接上大模型
2.1 现状与版本
- Spring AI 1.x 是生产可用的稳定版本
- Spring AI 2.0 基于 Spring Boot 4,目前处于里程碑(Milestone)阶段——尝鲜可以,生产建议先锁 1.x
- MCP(Model Context Protocol)支持已并入 Spring AI 2.0 核心模块,Spring 应用可以同时作为 MCP Server 或 Client 接入智能体生态
2.2 核心能力速览
@RestController
@RequiredArgsConstructor
public class ChatController {
private final ChatClient chatClient;
@GetMapping("/chat")
public String chat(@RequestParam String q) {
return chatClient.prompt().user(q).call().content();
}
// 流式输出(配合 03 篇的 SSE)
@GetMapping(value = "/chat/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> stream(@RequestParam String q) {
return chatClient.prompt().user(q).stream().content();
}
// ⭐ 结构化输出:直接把模型响应映射成 Java 对象,不用自己解析 JSON
@GetMapping("/extract")
public ResumeInfo extract(@RequestParam String text) {
return chatClient.prompt()
.user(u -> u.text("提取简历要点:{text}").param("text", text))
.call()
.entity(ResumeInfo.class);
}
}2.3 RAG(检索增强生成)骨架
@Bean
public ChatClient ragChatClient(ChatClient.Builder builder, VectorStore vectorStore) {
return builder
.defaultAdvisors(new QuestionAnswerAdvisor(vectorStore)) // 自动检索 + 拼进上下文
.build();
}
// 文档入库
var docs = new TokenTextSplitter().apply(new TikaDocumentReader(resource).get());
vectorStore.add(docs);支持的向量库:PGVector、Milvus、Redis、Chroma、Qdrant、Elasticsearch 等,都有对应 Starter。
2.4 工具调用(Function Calling)
@Bean
@Description("查询指定城市的实时天气")
public Function<WeatherReq, WeatherResp> weatherFunction(WeatherService svc) {
return req -> svc.query(req.city());
}模型判断需要实时数据时,会自动调用你的 Java 方法——这是让大模型接入企业内部系统的关键机制。
2.5 Spring AI vs LangChain:诚实对比
| 维度 | Spring AI | LangChain (Python) |
|---|---|---|
| 生态新鲜度 | ❌ 慢一拍(新模型/新工具几乎都是 Python 先发 SDK) | ✅ 第一时间跟进 |
| 与现有系统集成 | ✅ 直接复用 Spring 的安全、事务、可观测性、配置管理 | 需要自己搭一套工程化基础设施 |
| 类型安全 | ✅ 编译期检查,结构化输出直接映射对象 | 动态类型,靠 Pydantic 补 |
| 生产运维 | ✅ Java 生态的监控、调优、部署经验可直接复用 | Python 服务的运维成熟度普遍较低 |
| 团队技能匹配 | Java 团队零学习成本 | 需要 Python 技能 |
选型建议:算法探索和原型验证用 Python,生产化的 AI 应用集成用 Spring AI。 一个常见且有效的架构是——模型推理服务用 Python(FastAPI + vLLM),业务编排和企业系统集成用 Spring AI,两者通过 HTTP/gRPC 对接。别为了"技术栈统一"强行让 Java 做模型训练,也别为了"AI 就该用 Python"把整个企业后端重写。
3. GraalVM 原生镜像:算清这笔账
mvn -Pnative native:compile
./target/myapp # 启动 50-100ms| 维度 | 传统 JVM | Native Image |
|---|---|---|
| 启动时间 | 1-3 秒 | 50-100 毫秒(数量级差异) |
| 内存占用 | 200MB+ | 几十 MB |
| 峰值吞吐 | ✅ JIT 预热后更优 | ❌ 略逊(AOT 缺少运行时 profile 信息) |
| 构建时间 | 秒级 | ❌ 几分钟到十几分钟 |
| 反射/动态代理 | 完全支持 | ⚠️ 需要元数据配置(Spring Boot 已大幅自动化) |
| 调试/诊断 | 生态成熟(Arthas、JFR、jmap 全能用) | ❌ 工具链受限,生产排查困难 |
真正适合的场景:
- ✅ Serverless / FaaS(冷启动是核心指标,一切为启动速度让路)
- ✅ CLI 工具、Operator、Sidecar(短生命周期进程)
- ✅ 极高密度部署(一台机器塞几百个实例,内存是硬约束)
不适合的场景:
- ❌ 长期运行的常规 Web 服务——JVM 预热后的峰值吞吐更好,而启动那两秒一年也就影响几十次
- ❌ 大量使用反射、动态代理、字节码增强的应用(很多国产中间件、APM 探针会有兼容问题)
- ❌ 团队缺少排查经验时——生产上出问题,原生镜像的诊断手段远少于 JVM
Boot 4 的进步:Spring Boot 4 对原生镜像的支持比 Boot 3 时代成熟很多,加上 Spring Data AOT 把查询生成从运行时提前到编译期(官方数据显示带来 50%-70% 的启动速度提升),原生镜像正从"实验性尝鲜"走向"特定场景生产可用"。
但请诚实地问自己:你的服务启动慢 2 秒,真的造成业务损失了吗?如果答案是否定的,这笔投入(构建时间变长、排查难度上升、兼容性风险)就不划算。
4. Spring Modulith:拯救单体的架构利器
4.1 它解决什么问题
单体项目的真实腐化路径:一开始分了 order、user、payment 三个包 → 半年后每个包都能直接调用其他包的内部类 → 依赖关系变成一团乱麻 → 想拆微服务时发现根本拆不动。
Spring Modulith 的做法:在单体内部用代码强制模块边界,并且可以自动验证。
// package-info.java:声明模块及其允许的依赖
@ApplicationModule(allowedDependencies = { "user", "shared" })
package com.example.order;@Test
void verifyModuleStructure() {
ApplicationModules.of(Application.class).verify(); // ⭐ 违反边界直接测试失败
}
@Test
void generateDocs() {
new Documenter(ApplicationModules.of(Application.class))
.writeDocumentation(); // 自动生成 PlantUML 架构图和模块 canvas
}它还提供:模块间基于事件的松耦合通信、事件发布日志(持久化事件,保证不丢)、按模块的集成测试切片(@ApplicationModuleTest 只启动一个模块)。
为什么这是 2026 年最被低估的 Spring 项目:行业正在从"微服务狂热"回摆到"模块化单体"。Modulith 让你先把边界画清楚并用 CI 强制守住,等业务验证了边界稳定、团队规模真的顶不住了,再把模块提取成服务——这时候拆分的成本最低、风险最小。
对比来说:先拆微服务再发现边界画错了,那是真正的灾难级重构。
5. Spring Batch:千万级数据处理
@Bean
public Job importJob(JobRepository repo, Step step) {
return new JobBuilder("importJob", repo).start(step).build();
}
@Bean
public Step importStep(JobRepository repo, PlatformTransactionManager tx,
ItemReader<RawRecord> reader,
ItemProcessor<RawRecord, CleanRecord> processor,
ItemWriter<CleanRecord> writer) {
return new StepBuilder("importStep", repo)
.<RawRecord, CleanRecord>chunk(1000, tx) // ⭐ 每 1000 条提交一次事务
.reader(reader).processor(processor).writer(writer)
.faultTolerant()
.skipLimit(100).skip(ValidationException.class) // 容忍最多 100 条脏数据
.retryLimit(3).retry(DeadlockLoserDataAccessException.class)
.build();
}为什么不用普通的 @Scheduled + for 循环:
| 能力 | 手写循环 | Spring Batch |
|---|---|---|
| 断点续跑 | ❌ 跑到 80 万条挂了,从头再来 | ✅ 从上次失败的 chunk 继续 |
| 事务粒度 | 要么一个大事务(撑爆),要么每条一个(慢) | ✅ chunk 级事务,粒度可调 |
| 跳过脏数据 | 自己写 try-catch | ✅ 声明式 skip/retry 策略 |
| 执行记录 | 自己记 | ✅ 元数据表自动记录每次执行的详情 |
| 并行处理 | 自己写线程池 | ✅ 多线程 Step / 分区 Step |
判断标准:数据量超过百万级、或者"跑失败了需要从断点继续"的场景,就该用 Spring Batch,别硬撑着手写。它最大的价值是断点续跑——一个跑 6 小时的任务在第 5 小时挂了,能从断点继续和从头再来,是完全不同的运维体验。
6. 其他值得知道的扩展
| 项目 | 解决什么 | 什么时候用 |
|---|---|---|
| Spring Session | Session 存到 Redis,多实例共享 | 传统 Session 认证 + 多实例部署;无状态 Token 方案就不需要了 |
| Spring Integration | 企业集成模式(EIP)实现 | 复杂的消息路由、协议转换、遗留系统对接 |
| Spring GraphQL | GraphQL 支持 | 前端需要灵活取字段、要解决 REST 的 over/under-fetching |
| Spring Shell | 交互式命令行应用 | 运维工具、内部 CLI |
| Spring Cloud Stream | 屏蔽 MQ 差异的统一编程模型 | 需要在 Kafka/RabbitMQ 间切换,或多种 MQ 混用 |
| Spring Retry | 重试(Framework 7 已内建 @Retryable) | Boot 4 之后核心已支持,老项目仍在用这个库 |
REST vs GraphQL vs gRPC 选型
| 维度 | REST | GraphQL | gRPC |
|---|---|---|---|
| 协议 | HTTP + JSON | HTTP + 查询语言 | HTTP/2 + Protobuf |
| 性能 | 中 | 中(服务端解析查询有开销) | ✅ 最高(二进制、多路复用) |
| 前端灵活度 | ❌ 字段固定,容易 over-fetch | ✅ 按需取字段 | ❌ 固定契约 |
| 调试 | ✅ curl/浏览器直接看 | 中(需要 GraphiQL 等工具) | ❌ 二进制,要专用工具 |
| 缓存 | ✅ HTTP 缓存天然可用 | ❌ 难(POST + 动态查询) | ❌ 难 |
| 适合 | 对外 API、通用场景 | BFF 层、字段需求多变的前端 | 内部服务间高频调用 |
最常见的组合:对外 REST(通用、易调试、可缓存)+ 内部 gRPC(高性能)+ 复杂前端场景用 GraphQL 做 BFF。不要在一个系统里全部用同一种,也不要为了时髦把对外 API 全改成 GraphQL——你会失去 HTTP 缓存、CDN 和一大堆现成的网关能力。
7. 2026 年学习优先级排序(给你的时间分配建议)
| 优先级 | 技术 | 理由 |
|---|---|---|
| 🔥 必学 | 虚拟线程 | 改变架构决策逻辑,改造成本极低,收益立竿见影 |
| 🔥 必学 | Boot 4 / Framework 7 迁移知识 | 3.x 已停止免费社区支持,升级是必然,早学早主动 |
| 🔥 必学 | 可观测性(Metrics/Tracing) | 分布式系统的基本生存技能,不是可选项 |
| ⭐ 强烈推荐 | Spring AI | 正在重塑后端工程师的能力边界,早入场早有话语权 |
| ⭐ 强烈推荐 | Spring Modulith | 架构思维的升级,对带团队的人价值尤其大 |
| ⭐ 推荐 | API 版本化(Framework 7) | 解决了一个长期只能靠土办法的真实痛点 |
| 💡 按需 | GraalVM Native | 只在 Serverless/极致冷启动场景值得投入 |
| 💡 按需 | Spring Batch | 有大数据量批处理需求时再学,学起来很快 |
| 💡 按需 | WebFlux | 虚拟线程之后适用面收窄,但网关/流式场景仍不可替代 |
| ⏸️ 观望 | R2DBC | 生态不成熟,虚拟线程进一步削弱了它的必要性 |
8. 本篇自测
- 虚拟线程和 Goroutine 的调度模型有什么异同?虚拟线程对 Java 生态最大的意义是什么?
- Pinning 是什么?怎么排查和规避?
- 为什么说"开启虚拟线程后瓶颈会转移而不是消失"?该重新评估什么?
- Spring AI 相比 LangChain 的优势和劣势分别是什么?推荐怎么组合使用?
- GraalVM 原生镜像的真实收益和代价?什么场景值得,什么场景不值得?
- Spring Modulith 解决了什么问题?为什么说它对"要不要拆微服务"这个决策很关键?
- Spring Batch 相比手写定时任务循环,最核心的优势是什么?
- REST / GraphQL / gRPC 该怎么组合使用?
下一篇(08 · 实战与避坑):一个完整的生产级项目骨架、性能调优的完整方法论、四个真实生产事故复盘(连接池打满、缓存雪崩、线程池拒绝、内存泄漏),以及一套分层级的面试题库——从"能用"到"能架构",每一层该会什么。