Spring AI 2.0实战:Java后端不写Python,10分钟集成大模型与RAG
2026/9/9 1:51:36 网站建设 项目流程

我先把话放这儿:做Java后端久了,你迟早会遇到“想在自己的业务系统里加一个AI能力”的需求。过去一刷教程,满屏都是Python环境、conda、pip install、虚拟环境,项目还没跑起来就已经想放弃了。Spring AI 2.0真正改变的是这件事——把大模型集成变成Spring Boot项目里一个普通starter的事情。你不需要懂Python,不需要维护一套独立的大模型服务,只需要会用Maven和写Controller,10分钟就能让业务系统开口说话。这篇文章我就按自己实际接入的流程来写,从选型、配置、跑通到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-ollamaspring-ai-starter-model-openai这样的命名方式,老版本里的spring-ai-ollama-spring-boot-starterspring-ai-openai-spring-boot-starter也在兼容。

如果你和我一样是从1.x时代用过来的,会发现核心思路没变,还是ChatModelChatClientEmbeddingModelVectorStore这套抽象。2.0把一些内部实现打磨得更干净了,比如ChatClient的流式调用、可观测性支持、以及RAG组件之间的衔接都比1.x顺手。如果你的项目还在Spring Boot 3.x上,用1.0/1.1完全没问题;如果愿意尝试新基线,直接用2.0,API写起来很舒服。

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都要求17+)
  • Maven 3.9+
  • Ollama(本地跑大模型最省事的工具,下载安装后直接可用)

Ollama安装好之后,在终端执行:

ollama pull qwen3:8b

也可以拉其他模型,比如llama3.1:8bqwen2.5:7b。我习惯用qwen3:8b是因为中文效果好、资源占用也能接受。模型拉取完成后,Ollama默认监听在localhost:11434,后面Spring AI就是往这个端口发请求。

提示:第一次启动Ollama会自动下载模型,如果网络慢会等一段时间。模型文件几个GB很正常,别着急。

2.2 创建Spring Boot项目并引入依赖

我习惯在 start.spring.io 上生成基础项目,也可以直接在已有工程里加依赖。关键依赖就一个:

<dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-model-ollama</artifactId> <version>2.0.0</version> </dependency>

如果你用的不是2.0,而是1.x版本,就换老坐标:

<dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-ollama-spring-boot-starter</artifactId> <version>1.1.2</version> </dependency>

这里要注意,Spring AI的版本迭代快,Maven Central和Spring仓库不一定完全同步。稳妥的做法是在pom.xml里显式加上Spring仓库:

<repositories> <repository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://repo.spring.io/milestone</url> <snapshots> <enabled>false</enabled> </snapshots> </repository> </repositories>

2.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> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-model-openai</artifactId> <version>2.0.0</version> </dependency>

然后配置base-url和api-key:

spring: ai: openai: base-url: ${AI_BASE_URL} api-key: ${AI_API_KEY} chat: options: model: ${AI_MODEL}

这里我把base-urlapi-keymodel都用了环境变量,避免把密钥提交到代码库。不管底层是哪个模型厂商,只要它提供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去管。

推荐的做法是定义一个配置类,提前构建好带默认行为的ChatClient

@Configuration 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时加上默认Advisor:

ChatClient chatClient = ChatClient.builder(chatModel) .defaultAdvisors(new MessageChatMemoryAdvisor(chatMemory)) .build();

每次调用后,对话内容会自动存入内存。对于单机demo、内部工具类场景,这个方案完全够用。生产环境如果有多实例部署,建议把ChatMemory换成Redis实现,否则会话状态会散落在不同节点上。

这里有个关键点:MessageChatMemoryAdvisor会按会话ID区分不同用户或不同会话。调用时可以通过请求参数指定会话ID:

String reply = chatClient.prompt() .user("给我推荐一个后端项目方案") .advisors(advisor -> advisor.param("chatId", sessionId)) .call() .content();

如果不传chatId,Advisor会使用默认会话ID,所有用户共享同一份上下文,这在真实项目里肯定不行。

3.3 关键参数:temperature、topP与maxTokens

不少初学者只关心能不能调通接口,忽略了模型参数对生成结果质量的影响。下面这几个参数是必须理解的:

参数作用建议值
temperature控制随机性,值越大回答越发散,越小越稳定0.2~0.8
topP核采样,控制候选词概率累加上限,和temperature二选一调节即可0.8~0.95
maxTokens限制单次回答最大token数,防止输出过长过多消耗根据场景设置,一般256~2048
stop自定义停止词,模型输出到该词就停止按需配置

我实际测试下来,做问答类应用,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_tokens,Spring AI内部做了转换,你按Spring AI的配置项写就行。

3.4 可观测性:用ObservationHandler监控AI调用

线上系统接入AI后,最担心的就是不可控:模型响应慢、调用失败、token消耗异常。Spring AI 2.0的解法是ObservationHandler,配合Micrometer,能快速拿到模型调用的耗时、token用量、状态等指标。

自定义一个简单的Handler:

@Component public class AiObservationHandler implements ObservationHandler<ObservationContext> { 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-modelembedding-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:向量存储,开发环境可以用SimpleVectorStore
  • EmbeddingModel:把文本转换成向量,本地用Ollama的embedding模型即可

第一次做RAG时,我建议先用最简方案:读一个Markdown文件,切块,转向量,存内存,然后检索并回答。

4.3 一个能跑的最小RAG实现

依赖先加Tika和向量存储相关模块:

<dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-tika-document-reader</artifactId> <version>2.0.0</version> </dependency>

拉一个embedding模型(本地走Ollama):

ollama 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负责让切块大小可控,SimpleVectorStoreEmbeddingModel完成向量化与检索,最终把检索结果拼接进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 热词里那个NoClassDefFoundError:Applet引发的血案

很多人按网上教程把Spring Boot版本升到4.0,然后启动项目就报:

java.lang.NoClassDefFoundError: java/applet/Applet

这个错误看着和AI无关,其实是JDK高版本把java.applet.Applet移除了,项目里某个老库(比如旧的支付SDK、Excel导出工具、PDF处理库)还在引用。排查思路是找到哪个依赖引用了Applet:

mvn 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与NL2SQL

6.1 国内大厂也在押注Java AI生态

前几个月阿里发布了Spring AI Alibaba项目,给Spring AI提供了DashScope(通义千问)的快速接入。也就是说,你可以用Spring AI那套API,无缝切换到底层是通义千问模型。依赖很简单:

<dependency> <groupId>com.alibaba.cloud.ai</groupId> <artifactId>spring-ai-alibaba-starter</artifactId> <version>1.0.0.0</version> </dependency>

然后配置spring.ai.dashscope.api-keyspring.ai.dashscope.chat.options.model即可。对于国内团队来说,数据合规和访问速度都有优势。

6.2 从自然语言到SQL:NL2SQL的真实用法

很多业务系统里,用户想查数据但不会写SQL,运营想统计指标还得排队等开发。NL2SQL在这类场景特别好使。Spring AI Alibaba提供了NL2SQL的示例,核心思路是:把数据库表结构信息作为上下文,让模型把用户问题转换为SQL,再交给JDBC执行。

大致流程:

  1. 读取数据库schema,整理成表名、字段名、字段注释的文本
  2. 把用户的自然语言问题连同schema一起作为Prompt
  3. 模型返回SQL
  4. 用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仓库拉下来跑一遍,再往自己项目里平移。这样你踩坑的半径会小很多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询