Skip to content

Spring 全栈硬核教程 · 07:前沿与扩展(Spring AI / 虚拟线程 / GraalVM / Modulith / Batch) ​

这一篇回答一个很现实的问题:2026 年,一个 Java 工程师该把有限的学习时间投在哪里?

前沿技术的价值不均等——有些会改变你写代码的方式(虚拟线程),有些只在特定场景值得(GraalVM),有些正在重塑一整个岗位的能力边界(Spring AI)。这一篇会诚实地告诉你每一项的真实收益、真实代价和适用边界,而不是无脑吹新。


1. ⭐ 虚拟线程:这一代最值得学的特性 ​

1.1 它解决了什么 ​

传统模型:一个请求 = 一个操作系统线程。线程贵(每个约 1MB 栈内存),创建慢,上下文切换成本高,所以只能用线程池复用。线程池大小一旦设定,就成了并发上限——阻塞在 I/O 上的线程,白白占着这个宝贵名额什么也不干。

虚拟线程:JVM 层面的轻量级线程,M:N 挂载到少量"载体线程"上。阻塞时,虚拟线程会被从载体线程上卸下,载体线程立刻去跑别的虚拟线程。于是"阻塞"的代价从"占死一个 OS 线程"降到"几乎为零"。

yaml
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 可能导致 Pinningchannel / mutex,无此问题
抢占依赖 JVM 的安全点基于信号的异步抢占(1.14+)
最大优势零改造升级存量 Java 代码从语言层设计,心智负担最低

对 Java 生态的真正意义:Go 的并发优势是"重写一切换语言"换来的;Java 的虚拟线程是"改一行配置"换来的。对手握几十万行存量 Spring 代码的团队,这个差别是决定性的。

1.3 三个必须知道的陷阱 ​

① Pinning(钉住)

java
// ❌ 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 核心能力速览 ​

java
@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(检索增强生成)骨架 ​

java
@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) ​

java
@Bean
@Description("查询指定城市的实时天气")
public Function<WeatherReq, WeatherResp> weatherFunction(WeatherService svc) {
    return req -> svc.query(req.city());
}

模型判断需要实时数据时,会自动调用你的 Java 方法——这是让大模型接入企业内部系统的关键机制。

2.5 Spring AI vs LangChain:诚实对比 ​

维度Spring AILangChain (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 原生镜像:算清这笔账 ​

bash
mvn -Pnative native:compile
./target/myapp     # 启动 50-100ms
维度传统 JVMNative 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 的做法:在单体内部用代码强制模块边界,并且可以自动验证。

java
// package-info.java:声明模块及其允许的依赖
@ApplicationModule(allowedDependencies = { "user", "shared" })
package com.example.order;
java
@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:千万级数据处理 ​

java
@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 SessionSession 存到 Redis,多实例共享传统 Session 认证 + 多实例部署;无状态 Token 方案就不需要了
Spring Integration企业集成模式(EIP)实现复杂的消息路由、协议转换、遗留系统对接
Spring GraphQLGraphQL 支持前端需要灵活取字段、要解决 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 选型 ​

维度RESTGraphQLgRPC
协议HTTP + JSONHTTP + 查询语言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. 本篇自测 ​

  1. 虚拟线程和 Goroutine 的调度模型有什么异同?虚拟线程对 Java 生态最大的意义是什么?
  2. Pinning 是什么?怎么排查和规避?
  3. 为什么说"开启虚拟线程后瓶颈会转移而不是消失"?该重新评估什么?
  4. Spring AI 相比 LangChain 的优势和劣势分别是什么?推荐怎么组合使用?
  5. GraalVM 原生镜像的真实收益和代价?什么场景值得,什么场景不值得?
  6. Spring Modulith 解决了什么问题?为什么说它对"要不要拆微服务"这个决策很关键?
  7. Spring Batch 相比手写定时任务循环,最核心的优势是什么?
  8. REST / GraphQL / gRPC 该怎么组合使用?

下一篇(08 · 实战与避坑):一个完整的生产级项目骨架、性能调优的完整方法论、四个真实生产事故复盘(连接池打满、缓存雪崩、线程池拒绝、内存泄漏),以及一套分层级的面试题库——从"能用"到"能架构",每一层该会什么。

基于 Vite 强力驱动 | 纯静态轻量托管