Java接入AI能力实战:Spring AI与HTTP裸调选型指南
1. 一个停更消息引发的技术选型焦虑前几天在技术群里看到有人转了一张截图说 Spring AI Alibaba 这个项目停更了群里瞬间就炸了锅。做 Java 后端的兄弟们反应特别大有人直接来了一句“Java 是不是彻底跟 AI 这波浪潮无缘了”还有人开始认真考虑要不要转 Python。我当时看到这个消息的第一反应是先别慌把事情的来龙去脉搞清楚再说。我自己做 Java 后端差不多十年了从最早的 SSH 时代一路做到现在的 Spring Boot 微服务架构中间也经历过无数次“Java 要完了”的论调。每次这种声音出来的时候我都会先问自己一个问题这个判断的依据是什么是某个具体项目停更了还是整个技术栈真的不行了这两者之间的差别可太大了。Spring AI Alibaba 这个项目说白了就是阿里系对 Spring AI 生态的一个补充实现主要解决的是在国内环境下对接各种大模型服务的问题。它停更这件事影响范围其实比很多人想象的要小得多。为什么这么说因为 Java 接入 AI 能力这件事从来就不是只有一条路可走。Spring AI 本身是 Spring 官方在维护的底层对接大模型的能力已经覆盖了主流方案而国内各家大模型厂商也基本都提供了标准的 HTTP API 接口用 Java 去调用这些接口本质上就是一个 REST 客户端的事情。所以这篇文章我想聊的不是“Spring AI Alibaba 停更了怎么办”而是更根本的一个问题Java 开发者到底该怎么接入 AI 能力有哪些靠谱的路径每条路径的优缺点是什么我会从实际落地的角度把 Java 接入 AI 的几种主流方案拆开来讲清楚包括技术选型的逻辑、具体的实操步骤、踩过的坑以及我个人的一些经验判断。不管你是刚接触 AI 集成的 Java 新手还是正在做技术选型的架构师应该都能从中找到对自己有用的东西。2. Java 接入 AI 能力的真实路径拆解2.1 为什么“某个项目停更”不等于“Java 没戏”先把这个逻辑理清楚。开源项目停更在技术圈是再正常不过的事情原因可能有很多维护者精力不够、公司战略调整、社区活跃度下降、被更好的方案替代等等。但一个项目停更只能说明这个项目本身的生命周期到了不能推导出整个语言生态出了问题。Java 在企业级开发领域的地位不是靠某一个 AI 框架撑起来的。银行、保险、电商、物流这些行业的核心系统绝大部分还是跑在 JVM 上的。这些系统现在都在想办法接入 AI 能力比如智能客服、文档摘要、代码辅助、数据分析等等。需求在这里供给就不会缺。事实上Spring AI 官方项目一直在迭代LangChain4j 这个专门为 Java 设计的 LLM 应用框架也活得挺好还有各种大模型厂商提供的 Java SDK选择面其实很宽。我自己的判断是Java 在 AI 应用层的机会反而比很多人想象的要大。因为 AI 最终要落地到业务系统里而业务系统大部分是 Java 写的。你不可能为了接一个 AI 功能把整个订单系统用 Python 重写一遍。更现实的路径是在现有的 Java 架构里通过 HTTP 调用或者 SDK 集成的方式把 AI 能力嵌进去。这个思路才是主流。2.2 三条主流技术路线的对比与选型逻辑Java 接入 AI 能力目前来看主要有三条路可以走。我把它们的核心特点整理成了一个表格方便你快速对比路线代表方案优势劣势适用场景官方框架路线Spring AI、LangChain4j抽象层次高、生态完善、功能全面学习成本较高、版本迭代快中大型项目、需要多模型切换SDK 直调路线各大模型厂商 Java SDK简单直接、控制精细、依赖少与厂商绑定、切换成本高快速验证、单一模型场景HTTP 裸调路线RestTemplate/WebClient零额外依赖、完全可控需要自己封装、重复劳动多轻量集成、特殊定制需求选哪条路核心看三个因素项目规模、团队技术储备、以及未来是否需要切换模型。如果是小团队快速验证一个想法直接用 HTTP 调 API 就够了没必要引入框架。如果是中大型项目需要长期维护和扩展那 Spring AI 或者 LangChain4j 这种框架会更合适。如果确定只用某一家的大模型服务那用厂商提供的 SDK 是最省事的。我个人的经验是大部分项目其实用不着上框架。很多团队一上来就引入一堆依赖结果发现核心逻辑就是拼一个 prompt、发一个 HTTP 请求、解析一下返回的 JSON。这种情况下框架带来的抽象反而增加了排查问题的难度。先把裸调跑通等业务复杂到需要管理多轮对话、工具调用、RAG 检索的时候再考虑引入框架这个节奏会更稳。2.3 停更事件对现有项目的影响评估如果你已经在项目里用了 Spring AI Alibaba现在最关心的问题肯定是要不要马上换掉我的建议是先别急着动。停更意味着不会有新功能和安全补丁但已经发布的功能不会突然消失。你需要做的是评估当前项目对这个库的依赖程度。具体来说分三种情况处理。第一种如果只是用了它对接某一家大模型的基础功能那完全可以自己写一个简单的 HTTP 客户端替代工作量不大。第二种如果用了比较深度的功能比如 RAG 检索、向量库集成那就需要评估迁移到 Spring AI 官方或者 LangChain4j 的成本。第三种如果项目还没上线那正好趁这个机会重新选型直接基于 Spring AI 官方版本来做。注意不要因为一个依赖停更就大规模重构。先评估影响面再决定动作。很多时候一个稳定的旧版本比一个频繁变动的新版本更可靠。3. 基于 Spring AI 官方框架的实操落地3.1 环境准备与依赖配置假设你现在要从零开始搭一个 Java 的 AI 应用我推荐直接用 Spring AI 官方版本。下面是一套完整的实操流程基于我最近一个项目的真实配置。首先JDK 版本建议用 17 或以上Spring Boot 用 3.2 以上。这两个是硬性要求因为 Spring AI 的很多新特性依赖较新的语言特性和 Spring 框架能力。Maven 依赖方面核心是这两个dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-core/artifactId version1.0.0-M5/version /dependency dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-openai-spring-boot-starter/artifactId version1.0.0-M5/version /dependency这里说明一下版本选择。Spring AI 在 1.0.0 正式版发布之前里程碑版本更新比较频繁M5 是我实测下来比较稳定的一个版本。如果你用的是其他版本API 可能会有细微差异建议以官方文档为准。配置文件里需要设置模型服务的地址和密钥spring: ai: openai: api-key: ${AI_API_KEY} base-url: https://api.example.com chat: options: model: gpt-4o-mini temperature: 0.7这里的base-url是关键。很多国内的大模型服务都兼容 OpenAI 的接口格式你只需要把地址换成对应厂商的端点就行。temperature参数控制输出的随机性0 到 1 之间值越低输出越确定值越高越有创造性。做客服问答这种场景建议设 0.3 左右做创意文案可以设 0.8 以上。3.2 核心对话功能的代码实现配置好之后写一个最简单的对话接口。Spring AI 提供了ChatClient这个抽象用起来很顺手RestController 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 请求、解析响应、处理异常。如果你用裸调的方式这些都要自己写。框架的价值就在这里它把重复的样板代码封装掉了。不过要注意ChatClient默认是无状态的每次调用都是独立的。如果你需要多轮对话得自己维护上下文。Spring AI 提供了ChatMemory接口来做这件事可以基于内存或者数据库实现。我一般用InMemoryChatMemory做原型验证生产环境换成基于 Redis 的实现。3.3 Prompt 模板与结构化输出处理实际项目里你很少会直接把用户输入原封不动发给模型。通常需要拼一个 Prompt 模板把系统指令、上下文信息、用户输入组合起来。Spring AI 的PromptTemplate可以帮你做这件事PromptTemplate template new PromptTemplate( 你是一个专业的客服助手。请根据以下信息回答用户问题。 产品信息{productInfo} 用户问题{question} 要求回答简洁不超过100字。 ); Prompt prompt template.create(Map.of( productInfo, 这款手机支持5G网络电池容量5000mAh, question, 这个手机电池耐用吗 ));结构化输出是另一个高频需求。比如你想让模型返回 JSON 格式的数据直接解析成 Java 对象。Spring AI 提供了BeanOutputConverter来做这件事BeanOutputConverterSentimentResult converter new BeanOutputConverter(SentimentResult.class); String format converter.getFormat(); PromptTemplate template new PromptTemplate( 分析以下文本的情感倾向{text}\n{format} );这里有个坑要注意不是所有模型都能稳定输出合法的 JSON。我实测下来GPT-4 系列和 Claude 系列表现比较好一些较小的模型经常会在 JSON 外面包一层说明文字。解决办法是在 Prompt 里明确要求“只输出 JSON不要任何其他内容”同时在解析的时候做容错处理比如用正则先提取 JSON 部分再解析。4. 裸调 HTTP 方案的完整实操4.1 为什么有时候裸调比框架更靠谱框架虽好但不是所有场景都适合。我遇到过好几次这样的情况项目里引入了一个 AI 框架结果版本升级导致 API 不兼容排查了半天才发现是框架内部的一个默认配置变了。这种问题在裸调方案里基本不会出现因为每一行代码都是你自己写的出了什么问题一目了然。裸调的核心就是用一个 HTTP 客户端发 POST 请求。Java 里可选的有RestTemplate、WebClient、OkHttp、HttpClient等。如果项目是 Spring Boot 的直接用RestTemplate最省事如果是响应式架构用WebClient如果追求极致性能可以用 JDK 11 自带的HttpClient。4.2 请求封装与异常处理的最佳实践下面是一个基于RestTemplate的完整封装示例我把它拆成了几个部分来讲public class AiClient { private final RestTemplate restTemplate; private final String apiKey; private final String baseUrl; public AiClient(String apiKey, String baseUrl) { this.apiKey apiKey; this.baseUrl baseUrl; this.restTemplate new RestTemplate(); // 设置超时时间这个很重要 SimpleClientHttpRequestFactory factory new SimpleClientHttpRequestFactory(); factory.setConnectTimeout(5000); factory.setReadTimeout(30000); this.restTemplate.setRequestFactory(factory); } public String chat(String message) { HttpHeaders headers new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); headers.setBearerAuth(apiKey); MapString, Object body new HashMap(); body.put(model, gpt-4o-mini); body.put(messages, List.of( Map.of(role, user, content, message) )); body.put(temperature, 0.7); HttpEntityMapString, Object request new HttpEntity(body, headers); try { ResponseEntityMap response restTemplate.postForEntity( baseUrl /v1/chat/completions, request, Map.class ); return extractContent(response.getBody()); } catch (HttpClientErrorException e) { // 4xx 错误通常是参数或鉴权问题 throw new AiServiceException(请求参数错误: e.getResponseBodyAsString()); } catch (HttpServerErrorException e) { // 5xx 错误服务端问题可以考虑重试 throw new AiServiceException(服务端错误请稍后重试); } catch (ResourceAccessException e) { // 超时或网络问题 throw new AiServiceException(网络超时请检查连接); } } }这段代码里有几个关键点值得展开说。超时设置是必须的不设的话默认是无限等待一旦服务端卡住你的线程池很快就会被占满。我一般设连接超时 5 秒读取超时 30 秒因为大模型生成回复有时候确实需要十几秒。异常分类处理也很重要4xx 和 5xx 的处理策略完全不同4xx 重试没有意义5xx 可以适当重试。4.3 流式输出的实现细节大模型生成回复有时候要好几秒用户等在那里体验很差。流式输出可以让内容一个字一个字地显示出来体验好很多。裸调实现流式输出需要用WebClient或者直接操作HttpURLConnection的输入流public FluxString chatStream(String message) { return webClient.post() .uri(baseUrl /v1/chat/completions) .header(Authorization, Bearer apiKey) .contentType(MediaType.APPLICATION_JSON) .bodyValue(Map.of( model, gpt-4o-mini, messages, List.of(Map.of(role, user, content, message)), stream, true )) .retrieve() .bodyToFlux(String.class) .filter(line - line.startsWith(data: )) .map(line - line.substring(6)) .filter(data - ![DONE].equals(data)) .map(this::parseDelta); }流式输出的数据格式是 SSEServer-Sent Events每行以data:开头最后以data: [DONE]结束。解析的时候要注意每个 chunk 里返回的可能是部分内容需要拼接起来。另外流式模式下错误处理会更复杂因为连接可能在中途断开要做好断线重连或者提示用户的准备。实操心得流式输出在弱网环境下容易断建议在前端加一个“重新生成”的按钮而不是自动重试。自动重试可能导致重复内容用户体验反而更差。5. 常见问题排查与避坑指南5.1 超时与连接池配置问题这是最常见的问题没有之一。表现是请求偶尔成功偶尔失败或者高峰期大面积超时。根本原因通常是连接池太小或者超时设置不合理。先看连接池。RestTemplate默认用的是SimpleClientHttpRequestFactory它底层是HttpURLConnection没有连接池的概念每次请求都新建连接。在高并发场景下这会导致大量 TIME_WAIT 状态的连接最终耗尽端口。解决办法是换成HttpComponentsClientHttpRequestFactory它基于 Apache HttpClient支持连接池PoolingHttpClientConnectionManager connectionManager new PoolingHttpClientConnectionManager(); connectionManager.setMaxTotal(200); connectionManager.setDefaultMaxPerRoute(50); HttpClient httpClient HttpClients.custom() .setConnectionManager(connectionManager) .build(); HttpComponentsClientHttpRequestFactory factory new HttpComponentsClientHttpRequestFactory(httpClient); factory.setConnectTimeout(5000); factory.setReadTimeout(30000);连接池大小的计算公式参考最大连接数 QPS × 平均响应时间。假设你的 AI 接口平均响应 3 秒峰值 QPS 是 20那最大连接数至少要 60。再留一倍余量设 120 到 200 比较稳妥。5.2 返回内容解析失败的排查思路模型返回的内容解析失败通常有三种原因。第一种是返回格式和预期不符比如你期望 JSON 但模型返回了 Markdown 代码块包裹的 JSON。第二种是内容被截断了因为max_tokens设置太小。第三种是编码问题中文内容出现乱码。排查的时候第一步永远是打印原始响应。很多人一上来就盯着解析代码看其实问题出在响应本身。把原始字符串打出来一眼就能看出是格式问题还是内容问题。如果是格式问题在 Prompt 里加一句“直接输出 JSON不要用代码块包裹”通常能解决。如果是截断问题把max_tokens调大或者让模型分多次输出。编码问题比较隐蔽表现是中文变成问号或者乱码。检查两个地方请求头的Content-Type有没有指定charsetUTF-8以及RestTemplate的消息转换器有没有配置 UTF-8。Spring Boot 默认是 UTF-8但如果你手动 new 了一个RestTemplate可能需要手动设置。5.3 速率限制与重试策略大模型服务通常都有速率限制超过之后会返回 429 状态码。处理 429 的正确姿势是退避重试而不是立即重试。立即重试只会让情况更糟因为你的请求还在配额之外。我一般用指数退避第一次等 1 秒第二次等 2 秒第三次等 4 秒最多重试 3 次。如果 3 次之后还是 429就返回一个友好的提示让用户稍后再试。Spring Retry 这个库可以很方便地实现这个逻辑Retryable( retryFor {HttpServerErrorException.class, HttpClientErrorException.TooManyRequests.class}, maxAttempts 3, backoff Backoff(delay 1000, multiplier 2) ) public String chatWithRetry(String message) { return chat(message); }另外本地也可以做一个简单的限流用 Guava 的RateLimiter或者 Resilience4j 的RateLimiter控制每秒发出的请求数不超过配额。这样可以从源头减少 429 的出现。5.4 常见问题速查表问题现象可能原因排查方法解决方案请求超时连接池不足、网络慢查看连接池监控、ping 服务端增大连接池、调整超时时间返回 401API Key 错误或过期检查 Key 配置更新 Key、检查环境变量返回 429超过速率限制查看响应头中的配额信息退避重试、本地限流JSON 解析失败返回格式不符打印原始响应调整 Prompt、增加容错解析中文乱码编码未指定检查 Content-Type设置 charsetUTF-8流式中断网络不稳定查看断点位置前端加重试按钮、后端记录断点6. 技术选型的个人经验与建议6.1 什么阶段该用什么方案我做了这么多项目总结下来一个规律方案的选择要和项目阶段匹配。在验证阶段怎么快怎么来直接用 HTTP 裸调一天就能跑通。在成长阶段业务逻辑开始复杂需要管理对话历史、处理多种模型这时候引入 Spring AI 或者 LangChain4j 是合理的。在成熟阶段系统需要高可用、可观测、可扩展这时候可能需要在框架之上再做一层自己的抽象把模型调用、Prompt 管理、结果缓存这些能力统一封装。很多团队犯的错误是在验证阶段就引入了重型框架结果被框架的各种概念和配置拖慢了进度。也有团队在成熟阶段还在用裸调代码里到处散落着 HTTP 请求和 JSON 解析维护成本极高。找到当前阶段最适合的方案比追求“最先进”的方案重要得多。6.2 多模型切换的架构设计思路如果你的业务可能需要切换模型那在架构设计上要提前留好口子。核心思路是定义一个统一的接口把不同模型的差异封装在实现类里public interface AiService { String chat(String message); FluxString chatStream(String message); } public class OpenAiService implements AiService { ... } public class AnotherModelService implements AiService { ... }然后在配置里指定用哪个实现通过 Spring 的ConditionalOnProperty或者自定义的工厂类来动态选择。这样切换模型的时候只需要改配置不需要动业务代码。Prompt 的管理也建议抽出来不要硬编码在 Java 代码里。可以放在配置文件、数据库或者专门的 Prompt 管理平台里。这样调整 Prompt 不需要重新部署运营人员也能参与优化。6.3 对 Java AI 生态的未来判断回到最开始的问题Java 还有希望吗我的答案是Java 在 AI 应用层的希望一直都很大只是这个希望不在“用 Java 训练大模型”上而在“用 Java 把大模型能力集成到业务系统”上。训练模型是 Python 的天下这个短期内不会变。但把模型能力落地到企业应用里Java 的优势非常明显成熟的工程体系、丰富的中间件生态、大量的存量系统。Spring AI 官方项目在持续迭代LangChain4j 的社区也很活跃各大模型厂商都在提供 Java SDK。这些信号都说明Java 在 AI 应用层的工具链在变得越来越好。停更一两个项目不影响整个生态的向前走。我个人的做法是保持对官方框架的关注但核心业务逻辑尽量不依赖特定框架。把模型调用抽象成自己的接口底层实现可以随时替换。这样不管上层框架怎么变我的业务代码都是稳定的。这个策略在过去几年的技术变化中帮我省了很多迁移的功夫。最后分享一个我踩过的坑不要为了用框架而用框架。我见过一个项目为了接一个简单的文本分类功能引入了一个完整的 AI 框架结果光是配置就花了两天最后发现用 20 行 HTTP 代码就能搞定。技术选型的时候多问一句“这个复杂度是必要的吗”能帮你省下很多时间。