在聊 Java AI 技术栈之前,先说一个我最近的真实经历:上个月在给一个后台系统做 AI 问答功能时,团队里不是没人会 Python,而是整个服务端基础设施全是 Java——Spring Boot、若依脚手架、MySQL,还有一堆历史遗留的微服务。硬要用 Python 另起一套 AI 服务,意味着要维护两套部署链路、两套监控、两套权限体系,光想想就头大。这时候,“Java 能不能直接接入 AI”就不再是技术偏好,而是架构取舍问题。
答案是肯定的。经过这几年的沉淀,Java 生态已经把 AI 相关的框架补得相当完整:有 Spring 官方出品的 Spring AI,有专门为 JVM 做的 LangChain4j,也有能在 JVM 里直接跑深度学习模型的 DJL。你要做的不是“要不要用 Java 搞 AI”,而是“用哪套技术栈、怎么组织你的 AI 工程”。这篇文章就是我基于实际项目经验,把整个 Java AI 全栈从框架选型到生产落地的要点捋一遍,目标是零基础的人看完能跑通第一个项目,有经验的人能拿去避坑。
1. 先看清地图:Java 在 AI 浪潮里到底缺什么
1.1 破除惯性思维:你不需要转行 Python
大部分 Java 开发者一听到“AI”,第一反应是“完了,得重新学 Python”。这个想法的源头并不难理解:深度学习框架 PyTorch 和 TensorFlow 的官方 API 都是 Python 优先,科研圈和算法圈也全在用 Python 做实验。但这里有个关键区分——训练模型和使用模型是两件完全不同的事。
训练模型需要频繁改网络结构、调参、看 loss 曲线,Python 的交互式开发确实合适。但一旦模型训练完,业务系统要做的是把它包装成一个服务,接收请求、返回结果。这部分工作,Java 的工程化优势是碾压级的:Spring Boot 的自动装配、微服务治理、分布式事务、成熟的监控体系,都是 Python 服务短期补不齐的。说白了,AI 应用开发里 80% 的代码不是模型代码,而是工程代码,而这恰好是 Java 的主场。
我见过太多团队为了一个简单的问答机器人,硬是招两个 Python 后端来维护一套 Flask 服务,结果部署、鉴权、日志全都要重新搭,最后还得跟 Java 这边的配置中心打通。与其这样,不如直接在 JVM 里把 AI 能力接进来,让 Java 服务自己具备调用模型、组织知识、管理会话的能力。
1.2 Java AI 技术栈的四层结构
一套完整的 Java AI 技术栈,我习惯分成下面四层来理解:
| 层次 | 职责 | 代表性组件 |
|---|---|---|
| 模型接入层 | 统一封装各家大模型 API,屏蔽差异 | Spring AI、LangChain4j、LangChain4j OpenAI 扩展 |
| 本地推理层 | 在 JVM 内直接跑深度学习模型或小模型 | DJL、ONNX Runtime Java API、Ollama Java Client |
| 数据与知识层 | 文档加载、切分、向量化、向量检索 | Tika、Spring AI VectorStore、pgvector、Milvus |
| 业务编排层 | 把 AI 能力封装成 REST API、定时任务、消息消费者 | Spring Boot、若依框架、XXL-Job、Spring Cloud |
这四层是递进关系:先通过模型接入层拿到“能对话”的能力,再通过知识层让模型“懂你的业务”,最后用业务层把能力暴露给前端和其他系统。接下来我讲的实战案例,就是沿着这条路径一层层搭起来的。
1.3 这套栈能解决什么、不能解决什么
聊技术栈之前先泼盆冷水:Java AI 框架解决的是 “接入、编排、运维” 的问题,它不会帮你从零训练一个大模型。你要训练自己的 GPT,那确实得去 Python 生态;但你要做的是“调用现成模型 + 接上公司内部知识库 + 做成一个稳定服务”,那 Java 这套栈完全够用,而且比 Python 方案更适合长期维护。
还有一个常见误区:以为用了框架就能避免写 Prompt、避免理解向量化。框架只是把模型调用、检索这些操作改成了 Java 方法,底层原理你该懂还是得懂。所以这篇文章的后半部分,我会把 RAG(检索增强生成)、Embedding(向量化)这些概念放到实战里讲清楚,而不是让读者背概念。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 框架选型实战:Spring AI、LangChain4j、DJL 到底怎么挑
2.1 Spring AI:官方嫡系,最大优势是“零适配成本”
Spring AI 是 Spring 官方在 2023 年底启动的项目,经历了一年多的迭代,现在已经发布了 1.0 正式版。它在 Spring Boot 生态里的体验,用“无缝衔接”来形容完全不夸张——只要你用 Maven 或 Gradle 引入一个 starter,然后在 application.yml 里配置模型地址,就能在业务代码里注入一个 ChatClient 开始调用。
它最大的价值不是造了某个独家模型能力,而是把整个 AI 应用的通用组件标准化了:
- 模型抽象:
ChatClient、EmbeddingModel统一接口,切换 OpenAI、通义千问、DeepSeek、Ollama,基本只改配置。 - 与 Spring Boot 深度集成:自动配置、属性绑定、Actuator 健康检查,全都继承 Spring 的成熟体系。
- Get early access to RAG 抽象:
DocumentReader、TextSplitter、VectorStore、QuestionAnswerAdvisor开箱即用,后面实战章节我会演示。
适合的场景:存量 Java 系统要做 AI 功能,或者团队已经深度依赖 Spring Boot。它的学习成本对这些团队来说几乎为零。
2.2 LangChain4j:更贴近 LangChain 原味,也更容易写出优雅代码
LangChain4j 是社区驱动的项目,目标是给 Python 生态的 LangChain 提供一个功能对等的 Java 版本。画个重点:它并不是简单的 API 翻译,而是带着 LangChain 的 Agent 思想重新用 Java 设计了一遍。
LangChain4j 的核心概念包括:
ChatLanguageModel:聊天模型抽象MessageWindow/ChatMemory:管理多轮对话上下文AiServices:把 LLM 接入到 Java 接口,支持结构化输出、工具调用(Function Calling)ContentRetriever:RAG 检索器
我个人体验是:LangChain4j 在 Agent / 工具调用 这块明显比 Spring AI 成熟。你定义一个 Java 接口,通过 AiServices 绑定实现,模型就能自动决定调用哪个方法去查数据库、查天气,这在做自动化助手类项目时简直痛快。
适合的场景:新项目从零做 AI 原生应用,需要复杂的 Agent 编排,或你更习惯编程式 API 的写法。
2.3 DJL 与本地推理:JVM 里跑模型的冷门王牌
如果只是调用云上 API,那 LangChain4j 和 Spring AI 都够。但有些场景你必须把模型部署在本地或私有网络环境:数据不能出机房、网络不稳定、或者只是想把一个小模型嵌入到边缘设备。这时候 DJL(Deep Java Library,AWS 开源)就是那个很少有人提但很能打的选项。
DJL 的价值在于:
- 统一封装了 PyTorch、TensorFlow、ONNX 的推理接口,你在 Python 里训练好的
.pt、.onnx模型,可以在 Java 里直接加载推理。 - 支持图像分类、目标检测、语义分割、自然语言处理等多种任务,不只是聊天。
- 自带 Model Zoo,训练好的 ResNet、BERT 等模型一行配置就能下载加载。
现在的 DJL 还能借助底层引擎调用 Hugging Face 上的 Transformer 模型,意味着很多开源的 embedding 模型、生成模型,JVM 这边也能用起来。
适合的场景:私有化部署、图像识别类 AI 功能、以及在 JVM 体系内部推理的能力需要。
2.4 一张表终结选择困难
选型永远没有标准答案,但我可以给你一个经过验证的判断框架:
| 业务场景 | 推荐框架 | 理由 |
|---|---|---|
| 已有 Spring Boot 后台,加 AI 功能 | Spring AI | 与现有工程无缝集成 |
| 需要大量 Agent / 工具调用 | LangChain4j | 工具调用和记忆管理更成熟 |
| 私有化部署,模型要在 JVM 内跑 | DJL + ONNX Runtime | 支持 PyTorch 模型直接推理 |
| 想公平对比各家大模型 | Spring AI 或 LangChain4j | 都支持多厂家切换 |
我的建议是:主力学 Spring AI,能覆盖你 80% 的日常需求;碰到复杂 Agent 场景再去啃 LangChain4j 的文档,两者并不互斥。事实上,现在的项目里我会两个都引,Spring AI 负责标准化的对话和 RAG,LangChain4j 负责那些需要模型自己决定调用链路的场景。
3. 零基础起步:Spring Boot 接上第一个大模型对话
3.1 环境准备:用本地模型端到端跑通
讲代码之前,先说一个痛点:如果直接接 OpenAI,你得有海外网络环境;接国内大模型,也要申请 API Key。对刚想尝试的人来说,这些环境准备很容易劝退。所以我推荐一条零成本路径:本地 Ollama + Spring AI。Ollama 是一个能在本地跑大模型推理的工具,支持 Llama 3、通义千问 Qwen 等开源模型,安装方便,而且完全不需要任何 API Key。
你只需要做三件事:
- 下载安装 Ollama(支持 Windows、macOS、Linux)。
- 打开终端执行
ollama pull qwen2.5:7b,把通义千问的 7B 模型拉到本地。 - 新建一个 Spring Boot 项目,JDK 版本建议 17 以上,Spring Boot 版本 3.4 以上。
然后在 pom.xml 里加入:
xml复制<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-ollama-spring-boot-starter</artifactId>
</dependency>
如果要管理 Spring AI 的 BOM 版本,在 dependencyManagement 里加上:
xml复制<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>1.0.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
配置文件 application.yml:
yaml复制spring:
ai:
ollama:
base-url: http://localhost:11434
chat:
model: qwen2.5:7b
到这里,一个什么都还没写、但是已经能和本地模型通信的 Spring Boot 应用就具备了。
3.2 三个接口跑通第一轮对话
核心代码其实少得惊人。先定义一个可注入的服务:
java复制import org.springframework.ai.chat.client.ChatClient;
import org.springframework.stereotype.Service;
@Service
public class ChatService {
private final ChatClient chatClient;
public ChatService(ChatClient.Builder builder) {
// 这里也支持通过 builder 配置默认参数
this.chatClient = builder
.defaultSystem("你是一个乐于助人的中文助手,回答尽量简洁。")
.build();
}
public String chat(String message) {
return chatClient.prompt(message)
.call()
.content();
}
}
然后暴露一个 REST 接口:
java复制import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/ai")
public class AiController {
private final ChatService chatService;
public AiController(ChatService chatService) {
this.chatService = chatService;
}
@PostMapping("/chat")
public String chat(@RequestBody String message) {
return chatService.chat(message);
}
}
启动项目后,用 curl 测一下:
bash复制curl -X POST http://localhost:8080/api/ai/chat \
-H "Content-Type: text/plain" \
-d "用一句话介绍你自己"
如果一切正常,本地模型会返回一段回答。这一段代码里你其实已经接触到 Spring AI 最核心的抽象 ChatClient——它可以理解成一个“对话的入口对象”,所有模型对话的构建、调用、解析都通过它完成。
3.3 Prompt 模板和流式输出:从“能用”到“好用”
上面的接口能用,但离“能上线”还差两个关键点。
第一是 Prompt 模板。直接把用户的原文拼成请求发给模型,既容易注入恶意内容,也无法统一业务规范。Spring AI 支持类似 Java 模板引擎的写法:
java复制// 定义在 resources/prompts/translator.st 文件里
// System: 你是一名专业的翻译,将文本翻译成{targetLanguage}
// User: {text}
public String translate(String text, String targetLanguage) {
PromptTemplate template = new PromptTemplate(
new ClassPathResource("prompts/translator.st").getInputStream());
Map<String, Object> params = Map.of(
"targetLanguage", targetLanguage,
"text", text
);
return template.render(params);
}
这样配置和代码就分离开了,产品经理要调话术时不用改代码。
第二是 流式输出。大模型生成一段文案通常要花几秒,如果等全部生成完再返回,用户会看到一个转圈圈的界面;如果改成流式输出,用户能实时看到文字一个个蹦出来,体验完全不是一个级别。Spring AI 对响应式编程支持很好:
java复制import reactor.core.publisher.Flux;
public Flux<String> streamChat(String message) {
return chatClient.prompt(message)
.stream()
.content();
}
Controller 也改成返回 Flux<String>,Spring 会自动以 text/event-stream 的格式把数据流推给前端。这里要提醒一句:如果你在网关层做了统一超时配置,流式接口很容易被网关掐断,这个坑在 5.4 节我会专门说。
4. 进阶项目:用 Spring AI 实现一个可落地的 RAG 问答助手
4.1 为什么必须引入 RAG:模型学过的知识会过期
你可能遇到过这种情况:问模型“我们公司的请假流程是什么”,它一本正经地编了一个流程,而且完全对不上你们的制度。原因很简单——通用大模型训练时候用的公开数据,根本不可能包含你公司的内部文档。这时候就得把内部知识“喂”给模型,RAG(Retrieval-Augmented Generation,检索增强生成)就是解决这个问题的主流方案。
RAG 的工作流程我用一句话概括:先搜后答。用户提问后,系统先去知识库里检索相关内容,把命中的文档片段拼进 Prompt,再让模型基于这些片段组织答案。这样模型的所有回答都有据可依,不依赖训练记忆,企业内部的制度文档、产品手册、FAQ 都能通过这个方式变成模型的“外挂知识”。
有个很生活化的类比:大模型像一个什么都略懂一点但分不清场合的实习生,RAG 就是在你提问的时候,提前把相关的规章制度摆到他面前,让他照着念,而不是凭印象瞎编。
4.2 数据准备:文档切分与 Embedding 向量化
实现 RAG 的第一步是准备知识库。假设你有一堆 PDF、Word 格式的内部文档,首先要让程序能读进来。Spring AI 内置了基于 Apache Tika 的文档解析器:
java复制import org.springframework.ai.reader.tika.TikaDocumentReader;
import org.springframework.ai.document.Document;
import org.springframework.core.io.FileSystemResource;
public List<Document> loadDocuments(String filePath) {
TikaDocumentReader reader = new TikaDocumentReader(new FileSystemResource(filePath));
return reader.get();
}
解析完的“文档”就是一个包含文字内容、元数据(文件路径、页码等)的对象。但如果把整本手册一次性塞给模型,问题很大:一方面 Prompt 超长,成本飙升;另一方面,检索粒度太粗,一条回复往往会混入无关内容。所以要切分:
java复制import org.springframework.ai.transformer.splitter.TokenTextSplitter;
public List<Document> splitDocuments(List<Document> docs) {
TokenTextSplitter splitter = new TokenTextSplitter();
return splitter.apply(docs);
}
TokenTextSplitter 会按 token 数量把长文档切成多段,默认配置下每段大约是几百个 token,并且会保留一定重叠来避免切断语义。这里我要划个重点:切分策略参数一定要根据你文档的类型去调——规章制度可以按条数切,操作手册可以按章节标题切,技术文档则往往需要自定义切分逻辑。切大了检索精度差,切小了上下文不完整,这个平衡后面避坑章节还会再展开。
切分完成之后,每个片段都要转成向量,也就是 Embedding(嵌入向量化)。直觉理解:Embedding 就是把一段文字映射成一个几百维的数字数组,含义相近的文本在向量空间里的距离更近。“请假流程”和“休假申请”意思接近,它们的向量距离就很小;和“食堂菜单”的向量距离就很大。这样检索时,只要计算提问向量和所有片段向量的相似度,就能找出最相关的内容。
代码上只需要一行:
java复制EmbeddingModel embeddingModel = ...; // Spring AI 自动注入
后面在向量库的 add 方法内部会自动完成向量化。
4.3 存储与检索:pgvector 作为知识库
向量化之后,所有片段向量需要找个地方存起来,方便后续做相似度检索。生产环境我推荐使用 pgvector——它是 PostgreSQL 的扩展,也就是说你原本的 PostgreSQL 可以直接当向量数据库用,不用多维护一套 Milvus 或 ES 集群,这对中小团队特别友好。
引入依赖:
xml复制<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-pgvector-store-spring-boot-starter</artifactId>
</dependency>
配置:
yaml复制spring:
ai:
vectorstore:
pgvector:
index-type: HNSW
distance-type: COSINE_DISTANCE
启动前先在数据库里启用扩展:
sql复制CREATE EXTENSION IF NOT EXISTS vector;
Spring AI 会把向量表自动建好。接下来写一个入库 Service,本质就是三行代码:
java复制@Service
public class KnowledgeService {
private final VectorStore vectorStore;
public KnowledgeService(VectorStore vectorStore) {
this.vectorStore = vectorStore;
}
public void importDocument(String filePath) {
List<Document> docs = loadDocuments(filePath);
List<Document> chunks = splitDocuments(docs);
vectorStore.add(chunks); // 这里内部会调用 EmbeddingModel 完成向量化
}
}
如果你想先快速验证效果,本地调试阶段也可以直接用 Spring AI 的 SimpleVectorStore(内存版),不用装数据库。等到要部署了再切 pgvector,接口不变。
4.4 组装服务:先检索、再生成、带引用
知识库里有了数据,最后一步就是把“先检索、再生成”组装成一个完整的问答服务。Spring AI 提供了一个非常方便的顾问组件 QuestionAnswerAdvisor,它能自动完成:检索 → 组装上下文 → 让模型基于上下文回答。代码量少到你会怀疑是不是漏了什么:
java复制@Service
public class RagService {
private final ChatClient chatClient;
public RagService(ChatClient.Builder builder, VectorStore vectorStore) {
this.chatClient = builder
.defaultAdvisors(QuestionAnswerAdvisor.builder()
.vectorStore(vectorStore)
.build())
.build();
}
public String ask(String question) {
return chatClient.prompt(question)
.call()
.content();
}
}
如果要在回答里带上来源引用,可以用返回体:
java复制public Answer askWithReference(String question) {
ChatResponse response = chatClient.prompt(question)
.call()
.chatResponse();
// response.getMetadata() 可以拿到检索到的文档来源
return new Answer(response.getResult().getOutput().getContent(), extractSources(response));
}
到这里,一个企业内部的文档问答助手就成型了:上传一批文档,用户提问,系统从文档里检索相关内容,组织出带来源的答案。我在实际项目里就是拿这个架构给公司知识库做了一个线上服务,前端只需要一个输入框和聊天窗口。
5. 生产环境避坑:这五个问题不解决,Demo 就是 Demo
5.1 成本失控:Token 与连接池的双重治理
第一个坑出现在我做完第一个 Demo 后,日志里发现调用量高得吓人。排查后发现,问题不是用户太多,而是多轮对话中的历史消息被无限累积。每次把全部历史聊天记录重新发给模型,token 消耗是成倍增长的。这里必须做上下文窗口管理,通俗说就是“只保留最近几轮对话 + 一个全局摘要”。
Spring AI 的 ChatClient 可以通过 Advisors 控制历史窗口:
java复制chatClient.prompt()
.advisors(a -> a.param(ChatMemoryAdvisor.TEXT_WINDOW, 10))
.user(message)
.call()
.content();
另一个隐藏的成本黑洞在 Embedding。每入库一篇文档、每检索一次,都要调用向量化模型。如果文档库有几万段文本,入库一次的 embedding 费用也不容小觑。我的经验是:文档尽量增量入库,别每次启动都全量跑;同时给 embedding 接口加个本地缓存,用文档内容的 hash 做 key,相同内容不重复向量化。
5.2 数据一致性:AI 接口也要有事务思维
Java 后端老生常谈的事务问题,在 AI 场景同样存在,而且更容易被忽视。举个我踩过的真实例子:批量入库知识库时,先调模型做向量化,再把向量写入数据库。如果第 10 篇文档向量化成功、写入数据库失败,那整个批次就处于“部分文档能检索到、部分检索不到”的中间状态。更麻烦的是,模型 API 调用是外部副作用,本身不受数据库事务保护。
解决方案分两层:
- 入库批处理加幂等:给每个文档片段生成内容 hash 作为唯一键,重新导入时只处理新增和变化的片段。
- 入库和检索分离:先把向量写入临时表,全部成功后再切到正式查询表,类似数据库的“影子表”思路。
如果用的是定时任务批量抓取文档,还建议引入分布式任务调度(XXL-Job 或 Spring 自带的 @Scheduled),并且任务要支持“失败重跑”。AI 调用失败是常态,不是异常,网络抖动、模型返回超时都可能发生,写完一批必须能整批重推。
5.3 权限安全:行级权限在 AI 场景里的正确姿势
这是很多人做 AI 应用时压根没想的事:知识库的权限隔离。一套系统往往有多租户、多部门、多角色,“销售部能检索到的文档,研发部不能看”,这个要求在传统后台系统里有成熟的权限框架,但 AI 检索一旦接入,很多人想当然地就把所有文档汇到一个知识库了。结果就是,用户问“公司定价策略”时,销售权限的人能拿到,研发权限的人也能拿到——严重越权。
解决方案并不复杂:给向量库里的每个文档打上权限标签,检索时先按用户权限过滤再算相似度。以 pgvector 为例,可以在文档元数据里加 deptId 或 tenantId 字段,入库时:
java复制Document doc = new Document(content, Map.of(
"tenantId", tenantId,
"deptId", deptId
));
检索时先拼过滤条件:
java复制SearchRequest request = SearchRequest.builder()
.query(question)
.topK(5)
.filterExpression("tenantId == :tenantId")
.bind(tenantId)
.build();
这就是所谓“行级权限”在 AI 场景的落地:权限过滤必须在向量检索之前完成,绝不能依赖模型自己去判断哪些内容能看哪些不能看。模型不懂权限,你喂给它什么它就用什么,喂错了就泄露了。
5.4 网关超时:流式接口与底层超时配置的博弈
流式输出虽然体验好,但在典型的企业架构里会命中一个经典问题:网关层超时。Spring Cloud Gateway 默认的 response-timeout 只有 30 秒左右,而大模型生成一段长文本经常要 20~60 秒。如果你用流式输出,只要客户端保持连接持续接收数据,网关通常不会掐断;但如果你图省事用普通接口一次性返回,超时几乎必然发生。
这里还要小心另一层超时:HTTP 客户端调用模型 API 的超时时间。Spring AI 底层用 RestClient,默认超时可能只有 5 秒,本地 Ollama 模型加载时间都经常超过这个数。所以必须显式配置:
yaml复制spring:
ai:
ollama:
base-url: http://localhost:11434
http-client:
read-timeout: 60s
connect-timeout: 5s
我的经验是:对外用流式接口,对模型调用的 HTTP 客户端把 read-timeout 放宽到 60~120 秒,同时给整个服务加一层熔断——比如用 Resilience4j 的 @CircuitBreaker 包裹带模型调用的方法,防止模型服务卡死时拖垮整个业务线程池。
5.5 检索质量之坑:切分策略是最隐蔽的挖坑点
很多人把 RAG 做出来之后觉得效果还行,可一换文档就翻车。问题基本出在文档切分上。我之前把一份产品手册整个塞给 TokenTextSplitter 用默认参数切,结果模型在回答“这个功能是否支持批量导入”时,引用的上下文里混入了另一功能模块的描述,答案前后矛盾。后来调查发现,默认切分只考虑 token 数,不考虑文档原有的标题层级和段落逻辑。
正确的做法是先分析文档结构,再决定切分策略:
| 文档类型 | 推荐策略 |
|---|---|
| 规章制度、条款类 | 按章节 / 条款编号切,保留编号 |
| FAQ 类 | 每题一条,问题+答案一起保留 |
| 操作手册类 | 按标题层级切,并保留父标题作为上下文 |
| 长文本论文类 | 按段落 + 重叠窗口,重叠 50~100 token |
Spring AI 的 TokenTextSplitter 支持自定义参数:chunkSize(每块 token 数)、chunkOverlap(重叠 token 数),一般重叠设为 10%~20% 比较保险。更精细的做法是自己写一个切分器,读取文档的标题、表格、列表结构后再切。调切分策略时别只看某一轮问答的效果,要多换几类问题测,特别是那些答案分布在多个章节的复杂问题。
6. 性能调优与部署:把 AI 服务当成正经业务系统来运维
6.1 并行调用:把多个模型请求变成并发任务
AI 请求是典型的慢 IO 操作,一次调用动辄几秒到几十秒。如果一个 Controller 里要串行调用两次模型(比如先优化问题,再基于优化后的问题检索回答),用户感受到的延迟就是两次调用的总和。此时只需简单改成并行调用,整个接口耗时就能砍掉将近一半。
Spring 的 CompletableFuture 或 Reactor 的 Flux 都适合干这个:
java复制public AiResponse parallelAnalysis(String question) {
CompletableFuture<String> optimizationFuture = CompletableFuture
.supplyAsync(() -> chatClient.prompt(question).call().content());
CompletableFuture<String> retrievalFuture = CompletableFuture
.supplyAsync(() -> knowledgeService.retrieve(question), aiTaskExecutor);
String optimizedQuestion = optimizationFuture.join();
String knowledgeContext = retrievalFuture.join();
// 再用上下文生成最终回答
return generateAnswer(optimizedQuestion, knowledgeContext);
}
但千万记得用单独的线程池 aiTaskExecutor,不然 CompletableFuture 默认用 ForkJoinPool.commonPool(),在高并发下容易把公共线程池塞满,连累其他非 AI 逻辑。线程池参数要看模型调用 QPS 设置,一般核心线程数 = 模型并发上限的 70% 左右。
6.2 异步任务:定时批量处理交给后台调度
不是所有 AI 调用都必须实时。有些场景天然适合异步:
- 用户上传一批文档,后台批量切分 + 向量化入库
- 定时爬取内部系统更新文档,增量刷新知识库
- 每晚批量生成摘要,第二天直接展示
这些任务如果用同步接口,前端会一直等;如果用户中途关掉页面,任务可能就断了。正确的设计是用后台调度框架把任务交给独立线程池执行,再通过消息通知用户结果。Spring Boot 自带的 @Scheduled 够用,多实例部署时建议升级到 XXL-Job,这样调度记录、失败重试、分片处理都有现成能力。
我实际项目的做法是:用户上传文档后,先把“导入任务”写进数据库,状态置为“处理中”,后台定时任务扫描待处理文档、调用 embedding、写向量库、更新状态。前端轮询任务状态即可,哪怕模型服务挂了一会儿,任务重试后也能续上。
6.3 部署选择:云端 API 还是私有化模型
选部署方式,本质上是在“快速见效”和“安全可控”之间做平衡:
| 维度 | 云端 API | 私有化模型(Ollama/DJL) |
|---|---|---|
| 部署成本 | 最低,开箱即用 | 需要 GPU 或高性能 CPU |
| 数据安全 | 数据出网,需合规评估 | 数据不出机房 |
| 推理性能 | 受限于网络和厂商限流 | 完全自控,可扩容 |
| 延迟 | 网络开销大 | 低,尤其本机部署 |
| 成本结构 | 按 token 计费,弹性 | 硬件投入固定,量越大越划算 |
我的建议是:先云端 API 跑通业务,等 QPS 稳定了再评估私有化。千万别一上来就买 GPU 服务器,因为模型选型可能随时变,硬件投资容易打水漂。如果确定私有化,Ollama 的部署非常简单,按官方文档两步走就行;要是在 JVM 里集成定制的 PyTorch 模型,就用 DJL 加载。
6.4 观测体系:让每一次调用都留痕
AI 服务调试最头疼的一点是不确定性——同一个问题两次回答可能不同。排查问题时,只靠业务日志根本不够,至少要留三类信息:
- 输入输出日志:用户问题、最终回答、模型名称、token 消耗、耗时
- 检索痕迹:RAG 命中了哪些文档片段、排序分数
- 链路追踪:从网关到业务服务再到模型服务的完整 trace 信息
Spring Boot 自带 Micrometer + Actuator,配上一套 Prometheus + Grafana 就能覆盖前两类。关键是自己写个简单的调用日志切面:
java复制@Around("@annotation(AiTracked)")
public Object logAiCall(ProceedingJoinPoint pjp) {
long start = System.currentTimeMillis();
try {
Object result = pjp.proceed();
log.info("AI调用成功, 耗时={}ms, 结果={}",
System.currentTimeMillis() - start, truncate(result));
return result;
} catch (Exception e) {
log.error("AI调用失败, 耗时={}ms", System.currentTimeMillis() - start, e);
throw e;
}
}
链路追踪用 Spring Cloud Sleuth 或者 Micrometer Tracing 都能接。观测不是可选项,没有输出日志和耗时监控,模型换了、Prompt 调了,你都说不清效果是变好还是变差。
7. 面试与学习路线:Java AI 经验如何沉淀成职业筹码
7.1 面试官想听到什么:工程化能力比调包更值钱
最近一年,Java 岗位面试里 AI 相关问题出现的频率肉眼可见地提高了。但你知道吗,面试官最烦的不是你没做过 AI 项目,而是你只会吹“我调用了大模型 API”,追问两句就露馅。真诚的建议是:与其在简历里写“熟悉 AI”,不如写一个具体的真实场景,比如“基于 Spring AI 完成了公司知识库 RAG 问答助手,负责文档切分策略、向量检索权限过滤、流式输出与成本治理”。一句话里包含框架、场景、职责、难点,这才是面试官想听的。
面试官大概率会顺着追问这几个点:
- 为什么用 RAG 而不是直接微调模型?(答:知识更新成本低、可追溯、避免幻觉)
- 向量化用什么模型?检索排序是怎么做的?(答:EmbeddingModel + 相似度 Top-K,聊清楚 metadata filtering 和重排思路)
- 多租户数据隔离怎么保证?(答:行级权限在向量检索前过滤,见我 5.3 节)
- 流式输出怎么处理网关超时?(答:SSE 长连接 + 调大 read-timeout + 熔断)
这些问题,只要亲手跑过一个 RAG 项目,基本都能答到点子上。没做过的人才会被问懵。
7.2 一条可执行的 100 天学习路线
最后给零基础读者一条我验证过的学习路径,按 100 天规划,每天投入 1~2 小时:
- 第 1~20 天:打底。快速过一遍 Spring Boot 基础(自动装配、REST API、配置文件),不需要精通,跑到能写 CRUD 接口即可。
- 第 21~40 天:接入模型。搭好 Ollama 本地环境,用 Spring AI 完成对话、流式输出、Prompt 模板这三件事。这一阶段目标是“跑通”,不是“深刻理解”。
- 第 41~70 天:做 RAG。把公司公开的文档或自己整理的资料入库,实现文档切分、向量化、检索、问答闭环。同时把 pgvector 装上,理解向量数据库和普通数据库的区别。
- 第 71~90 天:工程化。把前面做的东西部署到 Linux 服务器,加上日志监控、权限过滤、并发线程池调优,跑一个完整的接口压测。
- 第 91~100 天:总结沉淀。写一篇技术复盘,把选型理由、踩坑记录、架构图整理成文字。这个动作本身就是最好的面试准备。
这套路线不需要 GPU、不需要付费 API、不依赖英文文档能力,就是一台能装 Docker 的电脑加一点耐心。如果你能从第 1 天坚持到第 100 天,你在 Java AI 这条赛道上的实际动手能力,会比大多数只在简历上写“了解 AI”的人强出一个身位。
我自己从接触 Spring AI 到把它用进生产项目,走了不少弯路,最大的感受是:Java AI 技术栈的成熟度远比很多人想象中高,真正的门槛不在框架,而在你有没有把一个完整场景从头到尾交付过。哪怕只是给自己写一个本地知识问答工具,跑通一次 RAG 全流程,后面碰到企业需求就有了底气。希望这篇指南能帮你省掉那些我当年绕过的弯子。
