Spring AI 2.0实战:Java后端不写Python,10分钟集成大模型与RAG
我先把话放这儿做Java后端久了你迟早会遇到“想在自己的业务系统里加一个AI能力”的需求。过去一刷教程满屏都是Python环境、conda、pip install、虚拟环境项目还没跑起来就已经想放弃了。Spring AI 2.0真正改变的是这件事——把大模型集成变成Spring Boot项目里一个普通starter的事情。你不需要懂Python不需要维护一套独立的大模型服务只需要会用Maven和写Controller10分钟就能让业务系统开口说话。这篇文章我就按自己实际接入的流程来写从选型、配置、跑通到RAG和监控再把踩过的坑一并整理出来给同样被困在Java生态里的朋友一个能直接照做的参考。1. 内容整体设计与思路拆解1.1 为什么Java程序员需要一套自己的AI集成方案很多Java团队在接触大模型时第一反应就是“先搭个Python服务”。这个思路本身没错但落地时问题很多团队里不一定有人熟悉Python、运维要维护两套运行环境、Python服务还得通过HTTP和Java系统对接一不对齐就容易出幺蛾子。我在实际项目里见过太多这种“AI服务和业务系统各说各话”的架构最后都是靠一堆胶水代码勉强跑通。Spring AI 2.0的做法完全不同。它把对话、向量检索、记忆管理等AI能力抽象成Spring生态熟悉的Bean和AutoConfiguration。你要做的就是引入依赖、配置模型服务地址、注入ChatClient然后像调用普通service一样调用大模型。整体架构从“Java系统 Python服务 网络协议对接”简化成“Java系统 大模型API/本地Ollama”链路短了调试成本也直线下降。1.2 Spring AI 2.0的版本脉络与技术选型Spring AI从2025年年中开始频繁发版先有1.0.0 GA然后1.1再往后就是2.0系列。2.0主要以Spring Boot 4.0为基线同时对核心API做了不少重构。最直观的变化是模块命名更规范了2.0里用spring-ai-starter-model-ollama、spring-ai-starter-model-openai这样的命名方式老版本里的spring-ai-ollama-spring-boot-starter、spring-ai-openai-spring-boot-starter也在兼容。如果你和我一样是从1.x时代用过来的会发现核心思路没变还是ChatModel、ChatClient、EmbeddingModel、VectorStore这套抽象。2.0把一些内部实现打磨得更干净了比如ChatClient的流式调用、可观测性支持、以及RAG组件之间的衔接都比1.x顺手。如果你的项目还在Spring Boot 3.x上用1.0/1.1完全没问题如果愿意尝试新基线直接用2.0API写起来很舒服。1.3 告别Python依赖的底气来源提到“告别Python依赖”不是贬低Python而是说Java生态完全能独立完成大模型应用的闭环。底层的关键点在于现在绝大多数模型服务都暴露了OpenAI兼容的HTTP API协议是标准化的。Spring AI对这些协议做了统一封装底层是标准HTTP调用和语言无关。本地场景则用Ollama这类工具它本身就是独立程序也暴露OpenAI兼容接口Java直接调用就行。所以你可以把Spring AI理解成“大模型的JDBC驱动”。JDBC做了SQL统一Spring AI做了Prompt API统一。你用Java连接MySQL难道还要先写个Python中间层用AI也一样不用。2. 10分钟快速集成从零到第一个对话接口2.1 环境准备JDK、Maven与本地模型动手前先准备好这三样东西JDK 17及以上Spring Boot 4和Spring AI 2.0都要求17Maven 3.9Ollama本地跑大模型最省事的工具下载安装后直接可用Ollama安装好之后在终端执行ollama pull qwen3:8b也可以拉其他模型比如llama3.1:8b、qwen2.5:7b。我习惯用qwen3:8b是因为中文效果好、资源占用也能接受。模型拉取完成后Ollama默认监听在localhost:11434后面Spring AI就是往这个端口发请求。提示第一次启动Ollama会自动下载模型如果网络慢会等一段时间。模型文件几个GB很正常别着急。2.2 创建Spring Boot项目并引入依赖我习惯在 start.spring.io 上生成基础项目也可以直接在已有工程里加依赖。关键依赖就一个dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-model-ollama/artifactId version2.0.0/version /dependency如果你用的不是2.0而是1.x版本就换老坐标dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-ollama-spring-boot-starter/artifactId version1.1.2/version /dependency这里要注意Spring AI的版本迭代快Maven Central和Spring仓库不一定完全同步。稳妥的做法是在pom.xml里显式加上Spring仓库repositories repository idspring-milestones/id nameSpring Milestones/name urlhttps://repo.spring.io/milestone/url snapshots enabledfalse/enabled /snapshots /repository /repositories2.3 编写配置让Spring AI认识Ollama在application.yml里加配置spring: ai: ollama: base-url: http://localhost:11434 chat: options: model: qwen3:8b temperature: 0.7这就完成了。接下来是写Java代码调用。Spring AI的ChatClient是门面不管是对话还是带上下文都通过它来发起。2.4 写一个Controller让大模型拥有HTTP接口代码量少到有点不好意思但确实就这么多RestController RequestMapping(/ai) public class ChatController { private final ChatClient chatClient; public ChatController(ChatClient.Builder builder) { this.chatClient builder.build(); } GetMapping(/chat) public String chat(RequestParam String message) { return chatClient.prompt() .user(message) .call() .content(); } }然后把项目启动起来访问http://localhost:8080/ai/chat?message你好几秒钟内就能拿到模型回复。整个过程不需要任何Python环境也不需要你额外起服务。我第一次跑通时第一反应是“这也太简单了”。但确实Spring AI 2.0的核心体验就是“少配置、少代码、能跑就行”。2.5 接入OpenAI兼容API一个配置就能换模型如果你不想在本地跑模型而是想用云端API比如国内某些模型平台的OpenAI兼容接口Spring AI同样能接。以任意兼容OpenAI协议的服务为例只需引入对应模块dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-model-openai/artifactId version2.0.0/version /dependency然后配置base-url和api-keyspring: ai: openai: base-url: ${AI_BASE_URL} api-key: ${AI_API_KEY} chat: options: model: ${AI_MODEL}这里我把base-url、api-key、model都用了环境变量避免把密钥提交到代码库。不管底层是哪个模型厂商只要它提供OpenAI兼容的HTTP接口Spring AI都能统一处理。这也再次说明Java调用大模型的链路一点都不比Python复杂。3. 核心细节解析与实操要点3.1 ChatModel与ChatClient别再用裸接口拼Prompt了Spring AI 2.0里有两个角色新人经常混淆ChatModel底层模型操作的抽象负责处理单次对话请求返回模型回复。ChatClient更高层的编程门面提供了prompt().user()...call()...content()这种链式调用还支持Advisor机制可以在对话前后嵌入处理逻辑。如果你只是想试试最基本的能力直接用ChatModel没问题。但一旦涉及多轮对话、RAG、日志记录等复杂场景ChatClient几乎是唯一选择。我见过不少人在Controller里直接注入ChatModel然后手动拼历史消息数组写出来的代码又长又乱。正确姿势是把ChatClient封装好把历史消息交给ChatMemory去管。推荐的做法是定义一个配置类提前构建好带默认行为的ChatClientConfiguration public class ChatConfig { Bean ChatClient chatClient(ChatClient.Builder builder) { return builder .defaultSystem(你是一个乐于助人的AI助手请用简体中文回答。) .defaultAdvisors(new MessageChatMemoryAdvisor(new InMemoryChatMemory())) .build(); } }这样一来Controller里的代码就不用关心系统提示词、历史记录了只管传入用户问题就行。3.2 ChatMemory多轮对话从无状态到有状态默认情况下每次调用chatClient.prompt().user(message).call()都是一次独立对话模型记不住上下文。如果直接自己拼历史消息不仅代码难看token消耗还容易失控。Spring AI的解法是ChatMemory接口和MessageChatMemoryAdvisor。最简单的内存实现ChatMemory chatMemory new InMemoryChatMemory();然后在构建ChatClient时加上默认AdvisorChatClient chatClient ChatClient.builder(chatModel) .defaultAdvisors(new MessageChatMemoryAdvisor(chatMemory)) .build();每次调用后对话内容会自动存入内存。对于单机demo、内部工具类场景这个方案完全够用。生产环境如果有多实例部署建议把ChatMemory换成Redis实现否则会话状态会散落在不同节点上。这里有个关键点MessageChatMemoryAdvisor会按会话ID区分不同用户或不同会话。调用时可以通过请求参数指定会话IDString reply chatClient.prompt() .user(给我推荐一个后端项目方案) .advisors(advisor - advisor.param(chatId, sessionId)) .call() .content();如果不传chatIdAdvisor会使用默认会话ID所有用户共享同一份上下文这在真实项目里肯定不行。3.3 关键参数temperature、topP与maxTokens不少初学者只关心能不能调通接口忽略了模型参数对生成结果质量的影响。下面这几个参数是必须理解的参数作用建议值temperature控制随机性值越大回答越发散越小越稳定0.2~0.8topP核采样控制候选词概率累加上限和temperature二选一调节即可0.8~0.95maxTokens限制单次回答最大token数防止输出过长过多消耗根据场景设置一般256~2048stop自定义停止词模型输出到该词就停止按需配置我实际测试下来做问答类应用temperature设0.3到0.5比较稳做创意文案、头脑风暴可以调到0.8以上。maxTokens一定要设置不然一个不小心模型写篇小作文既费时间又费钱。在application.yml里配置spring: ai: ollama: chat: options: model: qwen3:8b temperature: 0.5 top-p: 0.9 max-tokens: 1024注意不同模型服务对参数名的兼容程度不同比如OpenAI兼容接口一般用max_tokensSpring AI内部做了转换你按Spring AI的配置项写就行。3.4 可观测性用ObservationHandler监控AI调用线上系统接入AI后最担心的就是不可控模型响应慢、调用失败、token消耗异常。Spring AI 2.0的解法是ObservationHandler配合Micrometer能快速拿到模型调用的耗时、token用量、状态等指标。自定义一个简单的HandlerComponent public class AiObservationHandler implements ObservationHandlerObservationContext { private static final Logger log LoggerFactory.getLogger(AiObservationHandler.class); Override public void onStart(ObservationContext context) { // 调用前日志 } Override public void onStop(ObservationContext context) { log.info(AI观测指标 stop, name {}, highCardinality {}, context.getObservation().getName(), context.getHighCardinalityKeyValues()); } Override public boolean supportsContext(ObservationContext context) { return true; } }Spring AI在调用时会自动创建Observation观察点名称类似chat-model、embedding-model等。通过ObservationRegistry配合MeterRegistry还能把数据导出到Prometheus、Grafana。至少要在本地把“每轮对话耗时”和“token消耗”记下来否则生产环境出了问题你根本不知道是模型接口慢了还是自己的业务代码慢了。4. RAG实操给大模型接上私有知识库4.1 先搞懂RAG解决的是什么问题大模型的训练数据有截止时间而且不包含企业内部文档。你要让它基于自己的产品手册、运维文档、规章制度回答光靠提示词塞不下那么多内容。RAG检索增强生成的思路是先把问题拿去检索知识库找到最相关的几段内容再连问题一起交给大模型生成答案。4.2 不用Python的RAG全家桶很多人一看到RAG就想到LangChain、向量数据库、Embedding模型脑子又开始疼。Spring AI把这些东西抽象成了几个简单的组件DocumentReader读文档TikaReader、JsonReader、PagePdfDocumentReader等TokenTextSplitter把长文本切成小块按token数控制块大小VectorStore向量存储开发环境可以用SimpleVectorStoreEmbeddingModel把文本转换成向量本地用Ollama的embedding模型即可第一次做RAG时我建议先用最简方案读一个Markdown文件切块转向量存内存然后检索并回答。4.3 一个能跑的最小RAG实现依赖先加Tika和向量存储相关模块dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-tika-document-reader/artifactId version2.0.0/version /dependency拉一个embedding模型本地走Ollamaollama pull nomic-embed-text代码实现Service public class RagService { private final VectorStore vectorStore; private final ChatClient chatClient; public RagService(VectorStore vectorStore, ChatClient.Builder builder, EmbeddingModel embeddingModel) { this.vectorStore vectorStore; this.chatClient builder.build(); } public void loadDocument(String filePath) { var reader new TikaDocumentReader(Resource.fromFile(filePath)); var documents new TokenTextSplitter().apply(reader.get()); vectorStore.add(documents); } public String ask(String question) { var retrieved vectorStore.similaritySearch(SearchRequest.query(question) .withTopK(3)); StringBuilder context new StringBuilder(); for (var doc : retrieved) { context.append(doc.getText()).append(\n); } String prompt 根据以下资料回答问题\n context \n问题 question; return chatClient.prompt().user(prompt).call().content(); } }这段代码没有用任何Python组件。TikaDocumentReader负责把PDF、Word、TXT、HTML都解析成纯文本TokenTextSplitter负责让切块大小可控SimpleVectorStore和EmbeddingModel完成向量化与检索最终把检索结果拼接进Prompt完成“增强”这一步。比较讲究的做法是给ChatClient配置QuestionAnswerAdvisor它是Spring AI专门为RAG准备的问答Advisor能自动完成“检索增强生成”三步ChatClient chatClient ChatClient.builder(chatModel) .defaultAdvisors(new QuestionAnswerAdvisor(vectorStore)) .build();加了这行后续调用chatClient.prompt().user(question).call()时框架会自动去VectorStore里检索相关内容并补进上下文连手工拼Prompt都不用了。5. 常见问题与排查技巧实录5.1 热词里那个NoClassDefFoundErrorApplet引发的血案很多人按网上教程把Spring Boot版本升到4.0然后启动项目就报java.lang.NoClassDefFoundError: java/applet/Applet这个错误看着和AI无关其实是JDK高版本把java.applet.Applet移除了项目里某个老库比如旧的支付SDK、Excel导出工具、PDF处理库还在引用。排查思路是找到哪个依赖引用了Appletmvn dependency:tree -Dincludes*:* -Dverbose | grep -i applet找到后看能不能升级到新版不能升级就通过exclusions排除旧依赖。如果项目里确实有老库离不开Applet别硬升Spring Boot 4老老实实用Spring Boot 3.5配Spring AI 1.x也完全够用。5.2 连接Ollama超时或拒绝连接最常见的原因是Ollama没启动或者模型没拉好。先检查curl http://localhost:11434在Spring AI里控制超时时间可以在配置里加spring: ai: ollama: base-url: http://localhost:11434 connect-timeout: 5s read-timeout: 60s如果模型没提前拉好第一次请求可能会等很久甚至超时建议启动项目前先手动ollama pull。5.3 HTTP 429限流与token超限怎么处理接云端API时429 Too Many Requests几乎一定会遇到。Spring AI本身没内置自动重试我的做法是用Spring Retry在Service层做重试并做指数退避Retryable( retryFor TooManyRequestsException.class, maxAttempts 3, backoff Backoff(delay 1000, multiplier 2) ) public String callAi(String message) { return chatClient.prompt().user(message).call().content(); }如果报上下文长度超限Maximum context length exceeded说明历史消息攒太多了。要么调小maxTokens要么在MessageChatMemoryAdvisor中按token数裁剪历史记录要么提升ChatMemory的窗口大小。生产环境建议对每轮上下文做token统计超限时自动清理最早的消息。5.4 Spring AI 1.x升级到2.0的适配重点从1.x升到2.0主要注意这几点引入的starter坐标变了老坐标会提示失效或产生冲突部分自动配置类包名有调整自定义配置时注意import路径Spring AI 2.0默认基于Spring Boot 4构建如果你项目还在3.x先别急着升实测下来代码层面改动不大ChatClient用法基本一致。最花时间的反而是Maven依赖调整和环境对齐。6. 进阶扩展Spring AI Alibaba与NL2SQL6.1 国内大厂也在押注Java AI生态前几个月阿里发布了Spring AI Alibaba项目给Spring AI提供了DashScope通义千问的快速接入。也就是说你可以用Spring AI那套API无缝切换到底层是通义千问模型。依赖很简单dependency groupIdcom.alibaba.cloud.ai/groupId artifactIdspring-ai-alibaba-starter/artifactId version1.0.0.0/version /dependency然后配置spring.ai.dashscope.api-key和spring.ai.dashscope.chat.options.model即可。对于国内团队来说数据合规和访问速度都有优势。6.2 从自然语言到SQLNL2SQL的真实用法很多业务系统里用户想查数据但不会写SQL运营想统计指标还得排队等开发。NL2SQL在这类场景特别好使。Spring AI Alibaba提供了NL2SQL的示例核心思路是把数据库表结构信息作为上下文让模型把用户问题转换为SQL再交给JDBC执行。大致流程读取数据库schema整理成表名、字段名、字段注释的文本把用户的自然语言问题连同schema一起作为Prompt模型返回SQL用JDBC执行并校验结果只开放只读权限防止危险操作我建议这类功能一定要加两层保护第一层只给模型看必要的表结构不要把所有库表都暴露出去第二层生成的SQL只允许SELECT禁止其他任何操作。另外线上环境不要直接执行模型生成的SQL先人工review或限制白名单表。6.3 Java工程师的AI技术演进路线做完这些基础能力后你可以在团队里继续扩展把ChatClient封装成公司内部AI网关统一鉴权、限流、日志把RAG知识库做成定时任务每天更新文档索引接入部门级Agent从用户输入到工具调用比如查询订单、创建工单结合微服务把AI能力注册成独立的spring-boot-starter供各业务线复用这条路走下来的终点不是“会用某个AI框架”而是让整个Java后端具备统一的、可观测、可治理的AI接入能力。这比零散地在各个项目里硬编码调用OpenAI SDK要有价值得多。7. 最后再分享一点实操体会说回标题里那句“告别Python依赖”它真正想表达的不是贬低Python而是让你意识到Java程序员完全可以用自己的技术栈完成AI应用开发。Spring AI 2.0把大模型集成做成了Spring生态该有的样子——依赖、配置、Bean、门面API全是Java开发者熟悉的味道。定位问题也不用再去Python进程里翻日志。根据我这段时间的实操体验最值得投入精力研究的是这三块一是ChatClient和Advisor的灵活用法它决定了你写业务代码时有多顺手二是RAG链路里文档切分和向量检索的质量这直接决定回答靠不靠谱三是可观测性没有观测AI调用就是一个黑盒出了问题只能靠猜。你把这三点吃透Spring AI在你手里就不会只是个“能对话的玩具”。另外版本迭代确实快这段时间Spring AI的发布频率几乎是每月一更所以在参考网上文章时建议先确认文章对应的版本再去翻官方文档对照模块名和API。换版本最稳妥的方式是把官方的example仓库拉下来跑一遍再往自己项目里平移。这样你踩坑的半径会小很多。