1. 为什么我强烈建议Java开发者关注Spring AI
过去一年,我身边几乎每个搞Java后端的朋友都在问同一个问题:团队里Python那边用LangChain写得飞起,Java这边有没有对应的轮子?有的,而且它已经不算新了——Spring AI。我第一次接触这个项目是在Spring生态的官方仓库里看到的,当时它还很早期,连1.0都没发到,但设计思路已经令我眼前一亮。它解决的问题非常朴素:让Java开发者能用统一的、Spring风格的方式去对接各类大模型,而不是每个模型一套SDK,每换一个厂商改一遍业务代码。
很多人把它类比成Java世界的LangChain,这个说法在思路上是对的,但实际上Spring AI更接近Spring家族一贯的做事方式——把复杂的东西包装成熟悉的抽象,让开发者在原有知识体系上平滑扩展。你在Spring Boot里怎么用RestTemplate、怎么用JdbcTemplate,在Spring AI里就怎么用ChatClient、ChatModel。这种一致性带来的上手成本极低,我实测下来,一个平时只写CRUD的Java工程师,从零到跑通第一个对话案例,大概只需要半小时。这个项目特别适合几类人:已经在做Spring Boot后端开发的团队、希望在不引入Python技术栈的情况下快速验证AI功能的产品小组、以及想低成本接触大模型API的独立开发者。
作为入门博文,我不会堆概念,接下来我会从设计思路讲起,带你把环境搭好,用最朴素的代码跑通一个真实可用的对话案例,然后聊一聊我踩过的坑。如果你之前完全没有接触过AI编程,只要懂Spring Boot基础,照着操作也能跑起来。
2. Spring AI的核心设计:统一抽象与和Spring生态的融合
2.1 统一模型抽象:它到底在抽象什么
大模型接入这件事,本质上就是“把文本请求发给模型接口,然后把模型返回的文本接回来”。听起来简单,但真正落到工程上就全是细节:不同厂商有不同的请求格式、不同的鉴权方式、不同的超时策略,有的模型还支持流式输出、多模态输入、function calling。如果你直接在各家SDK之上写业务代码,等要换模型或者同时接多个模型的时候,那酸爽程度不亚于在三个数据库方言之间手写迁移层。
Spring AI把这一层统一成了几个非常清晰的接口:ChatModel是聊天模型的总入口,ChatClient是面向业务代码的同步/流式调用门面,Prompt封装完整请求,ChatResponse封装模型响应。我在实际使用中最舒服的一点是,业务代码几乎不感知模型差异。你对接OpenAI和对接Ollama本地模型,写的调用代码是一样的,区别只在配置项——就像DataSource一样,一个接口,MySQL和PostgreSQL各自的Driver各不相同,但业务代码永远只面向DataSource编程。
2.2 Prompt与结构化输出:AI接口也可以强类型
很多人第一次接触Spring AI的时候会忽略Prompt对象的含义。它不只是“一段字符串”,而是把System消息、User消息、历史对话记录、输出格式要求、温度参数等打包成一个结构化的请求体。我更喜欢把它理解成“把开发者在网页聊天框里手动调参数的过程固化到代码里”。
Spring AI还内置了结构化输出解析。举个例子:你让模型返回一个JSON,它偶尔会夹带解释性文字或者markdown代码块,直接解析JSONObject会炸。Spring AI的BeanOutputConverter可以指定目标类型,它在构造Prompt时就把“只输出JSON、不要任何解释”写进System消息,并且能把返回文本自动映射到Java对象上。我第一次用这个功能的时候有点意外,居然连这类最容易出错的格式对齐问题都内建处理了。
2.3 和Spring生态的联动:不是空降兵,是自家人
Spring AI真正区别于其他Java AI框架的地方,在于它不是“嵌在Spring里的一座孤岛”,而是融入了Spring的整个基础设施。你可以直接通过application.yml配置模型参数,可以像用@Service一样注册自定义组件,也可以借助RestTemplate、WebClient的既有经验去理解它底层的请求逻辑。更关键的是,它能直接复用Spring Boot的Actuator端点做健康检查,用Spring Cache缓存模型结果,用Spring Retry做重试策略。接入成本低到有些像“在已经建好的房子里多接了一根水管”,不需要你推倒重来。
3. 环境准备与项目初始化:先把路铺平
3.1 版本选择与依赖导入
按照我目前的最佳实践,建议先准备JDK 17及以上版本,配合Spring Boot 3.x。Spring AI的版本迭代在2025年下半年开始明显加速,早期的一些API在后续版本里有调整,比如曾经的OpenAiAutoConfiguration以及AiClient相关写法逐步被替换成以ChatClient和ChatModel为核心的新模型,所以你在网上搜索时可能会看到旧版教程,记得留意文章标注的版本。我用的是当前较新的稳定版本,如果你不确定选哪个,去官方文档找参考文档对应版本即可,核心思路没变。
Maven坐标方面,Spring AI提供了一组专门的Starter,典型像spring-ai-starter-model-openai、spring-ai-starter-model-ollama。在选择时有一个容易踩的坑:不要只用一个spring-ai-core就以为万事大吉,不同模型对应不同Starter,依赖引入错位会导致ChatModel注入失败。我个人入门阶段的建议是从OllamaStarter开始,因为它不依赖外部网络和API Key,调试起来最省心。
3.2 镜像仓库配置:这一项很多人没说透
国内拉取Spring AI依赖时,默认Maven中央仓库经常会因为网络问题卡住。我这里直接说我的做法:将阿里云公共仓库作为主要下载源,同时在settings.xml里保留中央仓库作为fallback。这样做的原因是Spring AI的部分依赖传递路径比较深,如果只配一个镜像,偶尔会出现某个传递依赖在镜像里还没有同步的情况,双仓库兜底能明显减少头疼程度。配置完仓库后,首次构建会下载不少依赖,耐心等待即可。
3.3 Ollama本地模型:最干净的入门环境
如果你不想在注册云厂商和申请API Key上花时间,我建议直接用Ollama跑一个本地小模型。安装Ollama之后,在命令行执行一条拉取命令即可把模型下载到本地,例如拉一个轻量对话模型。首次对话时模型需要在内存中加载,响应会比后续慢一些,但体验已经足够流畅。本地模型的好处是数据不出机器、无额外费用,网络隔离环境下也能运行,这对很多企业内部验证特别友好。入门阶段完全可以用它来跑通全链路,等理解了核心流程,再切换到云端模型就非常顺手了。
4. 入门案例实操:十分钟写出你的第一个AI对话接口
4.1 项目骨架与核心配置
我直接以实际操作为例。新建一个Spring Boot项目,JDK版本选择17,Spring Boot版本选择3.x。项目结构只需要一个启动类、一个Controller和一个Service,不需要额外引入web以外的组件。关键的依赖坐标和application.yml配置我先给出,解释放在代码后。
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-model-ollama</artifactId> </dependency> </dependencies>spring: ai: ollama: base-url: http://localhost:11434 chat: model: qwen2.5:7b options: temperature: 0.8 max-tokens: 2048这两段配置的作用,我用大白话解释一下:base-url是Ollama服务的地址,默认是本地11434端口;model指定你本地已经拉好的模型名称;temperature是控制模型回答“自由发挥程度”的参数,0到1之间,越高回答越发散,越低回答越保守,我常用0.8得到有内容但不至于乱跑的回复;max-tokens限制单次回复最大长度,防止某些场景下模型长篇大论把接口响应时间拖垮。
4.2 一个能直接复用的ChatClient配置
ChatClient是Spring AI提供的流式门面接口,它在较新版本中是推荐用法。我不建议直接用ChatModel裸调,因为ChatClient把很多繁琐的请求组装细节都收敛了。定义一个@Bean如下:
@Configuration public class AiConfig { @Bean public ChatClient chatClient(ChatClient.Builder builder) { return builder.build(); } }这段代码的作用是向容器中注册一个ChatClient实例,后面业务代码里直接注入即可。有个小提醒:如果你的依赖引入正确,ChatClient.Builder会自动被Spring装配好,不需要额外配置。这里的Builder模式让我想起来WebClient.Builder,保持了Spring一贯的设计感。
4.3 写一个简单的对话接口
Controller层的代码是最直接的:
@RestController @RequestMapping("/ai") public class AiController { private final ChatClient chatClient; public AiController(ChatClient chatClient) { this.chatClient = chatClient; } @GetMapping("/chat") public String chat(@RequestParam(name = "msg", defaultValue = "你好") String msg) { return chatClient.prompt() .user(msg) .call() .content(); } }这段代码的逻辑非常清晰:接收请求参数msg,把它作为用户消息发给模型,同步调用,最终返回模型生成的文本。.call()表示同步阻塞等待结果,.content()提取回复文本。整个流程让我想起用JdbcTemplate查数据——骨架固定、意图明确,唯一要关心的就是业务逻辑本身。
4.4 增加历史对话能力:改造为多轮对话
如果你想实现真正的连续对话,客户端每次把一个用户消息发给模型,服务端还需要把历史消息一起传过去。Spring AI里可以用ChatMemory或者在每次请求时手动拼接消息列表。对于入门案例,我更推荐先理解“手动拼接”的方式,因为它直观展示了请求的构成本质:消息数组。伪代码如下:
@RequestMapping("/chat/history") public String chatWithHistory(@RequestParam String msg, @RequestParam String history) { return chatClient.prompt() .system("你是一个乐于助人的助手,回答简洁直接。") .user(msg) .call() .content(); }这里的.system()方法设置了系统提示词,它决定了模型的“人设”和回答风格。多轮对话则是把历史消息按照system、user、assistant交替拼进Prompt,这跟直接调用模型API时组织消息列表的思路完全一致。一旦你理解了这个组织形式,再看任何大模型API文档都很轻松。
4.5 流式输出与JSON返回:入门后必做的两个增强
同步接口足够演示核心流程,但真实项目里往往需要流式输出——模型边生成边返回,像打字机一样把字敲出来。Spring AI的流式写法也非常简洁,调用.stream()替代.call(),拿到Flux<String>后通知前端。后面有需要做SSE推送的,这个能力会很关键。
第二个增强是让模型返回JSON。我用的BeanOutputConverter示例大致是这样:
public record BookInfo(String title, String author, int pages) {} BeanOutputConverter<BookInfo> converter = new BeanOutputConverter<>(BookInfo.class); String response = chatClient.prompt() .user("请以JSON格式返回一本你推荐的技术书籍信息") .system(converter.getSystemInstruction()) .call() .content(); BookInfo book = converter.convert(response);使用getSystemInstruction()之后,模型会被明确告知必须输出指定结构的JSON。实测下来,格式规范的比率远高于直接靠“请返回JSON”这种方式。有人可能会问,为什么不做成自动注入?因为不同业务对目标类型的要求差异太大,手动指定反倒让意图更清晰。
5. 实战中逃不开的坑:我的问题排查速查表
5.1 Bean注入失败:八成是starter引错了
芯片圈里有一句玩笑话叫“Debug就是找谁在偷摸改配置”,放到Spring AI这里很贴切。我遇到的第一类问题是运行时ChatClient.Builder或ChatModel无法注入,报错信息看起来像“找不到类型为ChatClient.Builder的Bean”。排查时先确认是不是Starter引错了,比如你只在pom里引入了spring-ai-core但没引入任何xxx-starter-model-xxx,Spring自然不知道要装配哪个模型实现。换个具体的模型Starter之后,这个问题基本消失。
5.2 连接本地模型失败:不只是地址写错
Ollama模式下的报错往往是Connection refused。很多人第一反应是检查base-url,但实际上还有几个容易忽略的点:第一,Ollama服务是否真的在运行;第二,端口是否被占用;第三,Ollama版本是否过旧导致接口不兼容。我的建议是先用curl直接请求一次Ollama的本地接口,确认能通再去查Spring配置。这一步能帮你快速把问题锁定在“网络层”还是“框架层”。
5.3 模型答非所问:先别怀疑模型,看看你的System提示词
很多初学者在案例跑通后,会急着把模型换到各厂商的大模型上,然后反馈“模型回答质量不行”。我的排查经验是:先看System Prompt是不是写得太空泛。你是AI助手和你是一名资深的Java架构师,回答时先分析需求,再给出代码示例,最后总结注意事项,前者几乎得不到有效输出。另外temperature设置太高也会让回答变得“飘”,需要稳定业务回答时我通常会调到0.3以下。
5.4 返回内容带Markdown代码块:结构化输出了解一下
早期我直接在代码里调用.content()拿结果然后尝试用Fastjson解析,遇到模型输出中夹着json和的情况怎么都解析不通过。换用BeanOutputConverter后问题直接解决,它不仅把格式要求写进System提示词,还能在反序列化时做容错处理。如果你外接的模型不支持这种严谨的指令遵循能力,也可以自己写一个后处理字符串的清理方法,去掉三段式代码块标记再交给JSON解析器。
5.5 超时问题:本地模型比云端模型更需要耐心
Ollama首次加载模型或者机器性能一般时,响应时间可能会到几十秒甚至一分钟以上。Spring AI默认的超时时间在实际使用中可能不够宽裕。遇到超时报错,我的做法是在Starter对应的配置属性里把响应超时调大,至少给到60秒。这里顺便解释下我为什么推荐先搞模型加载预热:把模型在本地跑起来,让它先完成一次生成,后面再调接口就快很多。
5.6 常见问题速查表
为了方便你按图索骥,我把上面几个问题整理成一张表格,实际排查的时候对照状态找原因即可。
| 现象 | 常见原因 | 解决思路 |
|---|---|---|
| 启动报错,找不到ChatModel | 缺少模型对应的Starter | 在pom.xml引入具体模型Starter |
| 调用时报Connection refused | Ollama服务未启动或端口异常 | 先curl本地接口确认服务可用 |
| 响应超时 | 模型加载慢或配置超时短 | 调大超时配置,提前预热模型 |
| 返回内容不是预期格式 | System提示词不明确 | 细化System Prompt,明确输出规范 |
| 解析JSON失败 | 返回内容夹带Markdown标记 | 使用BeanOutputConverter或做后处理 |
| 并发调用时内存占用高 | 本地模型完全加载到内存 | 调小模型参数量,或改用云端API |
5.7 日志查看技巧:把请求体打出来
如果你需要进一步调试,我个人强烈建议把Spring AI的请求日志打开,方法是在application.yml中为org.springframework.ai设置DEBUG级别。这样每次调用时都会打印出完整的Prompt内容和模型响应摘要,排查问题效率翻倍。我遇到过一种情况:业务代码上写的Prompt看着没问题,但打印出来发现历史消息的顺序是乱的,导致模型理解错误。有了日志,这个问题一眼就能看出来。
6. 从入门到进阶:两条值得深入的方向
6.1 走通RAG:让AI基于你的私有数据回答
单纯问答的Spring AI只是皮,真正的业务价值在于让模型“知道”你的数据。我当时接触RAG时的感受是:它的机制特别像“给AI配了一个知识库秘书”。核心流程是:把文档切块,用向量化模型转成向量,存入向量数据库;用户提问时,先把问题向量化,在知识库里检索最相关的片段,最后把片段组装进Prompt发给模型。Spring AI在这套流程里提供了非常自然的抽象,VectorStore接口屏蔽了多种向量数据库的差异,你自己实现一个类似“从本地知识目录加载文本”的服务,再配合Embedding模型就能串完整链路。
6.2 接Function Calling:让模型学会调你的服务
Function Calling是另一个值得尽早了解的能力。通俗解释:模型在回答一个问题时,发现需要某个实时数据,比如天气信息、库存数量、订单状态,它会把“调用某个函数”的意图以结构化参数返回给你,由你的业务系统真正执行函数,然后把函数结果返回给模型继续生成回答。Spring AI通过@Tool注解或函数定义的方式让Java方法暴露给模型,实现起来并不复杂。这代表着一个重要的方向转变:AI不只是文本转换工具,还能成为业务动作的编排者。
6.3 谨慎选择模型参数:别被默认值坑了
入门阶段最容易被忽略的参数是temperature和max-tokens。我见过有人直接不配置,用默认值跑业务,结果在需要精确输出的场景里经常得到啰嗦甚至重复的回答。做FAQ类问答,建议temperature调低;做文案生成、头脑风暴,可以调高。max-tokens如果给得太小,回答会被截断,模型生成到一半就停了,字符串会明显不完整。设置这些参数时的核心思路只有一个:根据具体场景的实验反馈来调整,而不是照搬任何人的默认值。
7. 写在最后:从我的实操体会说起
整套Spring AI入门流程走下来,我最大的感受是:这个框架把“接入大模型”的工程门槛降到了Spring Boot体系内,不需要额外技术栈,不需要学习新的请求规范,大部分精力可以留在业务建模上。它不是一个多么惊艳的创新,但它把一件原本有些凌乱的事情标准化了——就像当年Spring JDBC把数据访问统一起来一样。
如果你也是刚接触这块,我的建议不太一样:不建议一开始就搭复杂的RAG或Agent,先在本地模型上跑通一个最简单的对话接口,把Prompt、ChatClient、BeanOutputConverter这些概念亲手用一遍,再去尝试进阶能力。踩坑不可怕,可怕的是在没弄懂基础流程的时候一头扎进复杂系统,出了问题不知道是模型问题、网络问题还是代码问题。
最后分享一个小技巧:在你的Spring Boot工程里,把不同模型的配置封装成独立的配置类,切换模型时只需要调整@Profile或配置项,而业务代码不动。这样后续无论是引入新模型还是做A/B测试,都只需要面对配置文件的增删。AI应用开发还远没有到定型的阶段,但有一个顺手的基础框架,后面怎么折腾都会从容很多。