Spring AI 深度实战——Java 工程师用最熟悉的框架接入大模型
Spring AI 深度实战——Java 工程师用最熟悉的框架接入大模型
适读人群:Java 后端工程师、Spring 开发者 | 阅读时长:约18分钟 | 核心价值:用 Spring AI 从零到生产级接入大模型的完整实战路径
上周五下午,我们组的小王找到我,他在公司内部系统里做了一个 ChatGPT 接入的 Demo,直接用 HttpClient 手撸 HTTP 请求,把 API Key 写在代码里,流式响应也没处理,连超时都没配。他说领导让他这周上线。
我看了一眼他的代码,沉默了三秒,然后说:来,我给你讲讲 Spring AI。
如果你也是 Java 工程师,正在用各种奇奇怪怪的方式接大模型 API,这篇文章适合你。Spring AI 已经到 1.0.0 Release 了,能用在生产环境里了。
Spring AI 是什么,为什么值得学
Spring AI 是 Spring 官方出品的 AI 应用框架,定位跟 Python 的 LangChain 类似,但专门为 Java/Spring 生态设计。
核心卖点:
- 统一 API 抽象,底层换模型提供方不用改业务代码
- 原生支持 Spring Boot 自动配置,加几行依赖就能跑
- 内置 Prompt 模板、对话记忆、工具调用、RAG 等组件
- 支持 OpenAI、Azure OpenAI、Claude、Gemini、Ollama 等主流模型
对 Java 工程师来说最大的好处是:你不需要学 Python,用你最熟悉的 Spring 生态就能做 AI 应用。
环境准备
Spring Boot 3.2+,Java 17+,Maven 或 Gradle。
Maven 依赖:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>1.0.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 使用 OpenAI -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
</dependency>
</dependencies>application.yml 配置:
spring:
ai:
openai:
api-key: ${OPENAI_API_KEY}
base-url: https://api.openai.com # 换成你的代理地址
chat:
options:
model: gpt-4o-mini
temperature: 0.7
max-tokens: 2048最简单的对话接口
写一个最基础的 Controller,让你直观感受 Spring AI 的 API 风格:
@RestController
@RequestMapping("/ai")
public class ChatController {
private final ChatClient chatClient;
public ChatController(ChatClient.Builder builder) {
this.chatClient = builder
.defaultSystem("你是一个专业的 Java 技术顾问,回答要简洁准确。")
.build();
}
@GetMapping("/chat")
public String chat(@RequestParam String message) {
return chatClient.prompt()
.user(message)
.call()
.content();
}
}就这么简单。ChatClient.Builder 由 Spring 自动装配,你只需要注入就行。
测试一下:
GET /ai/chat?message=用一句话解释什么是Spring Boot响应(实测延迟约 1.2s,gpt-4o-mini):
Spring Boot 是一个基于 Spring 框架的快速开发工具,通过自动配置和嵌入式服务器简化了 Java 应用的创建和部署过程。流式响应——这个必须会
不做流式输出,用户盯着一个空白框等 5 秒,体验极差。Spring AI 的流式非常简洁:
@GetMapping(value = "/chat/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> chatStream(@RequestParam String message) {
return chatClient.prompt()
.user(message)
.stream()
.content();
}前端用 SSE 接收即可。实测 gpt-4o-mini 首 token 延迟约 400ms,这个体验比等全量响应好太多了。
Prompt 模板管理
硬编码 Prompt 是新手最常见的问题。Spring AI 提供 PromptTemplate:
@Service
public class CodeReviewService {
private final ChatClient chatClient;
// 从 classpath 加载 prompt 模板文件
@Value("classpath:prompts/code-review.st")
private Resource codeReviewPrompt;
public CodeReviewService(ChatClient.Builder builder) {
this.chatClient = builder.build();
}
public String reviewCode(String language, String code) {
return chatClient.prompt()
.user(u -> u.text(codeReviewPrompt)
.param("language", language)
.param("code", code))
.call()
.content();
}
}src/main/resources/prompts/code-review.st:
请对以下 {language} 代码进行 Code Review,重点关注:
1. 潜在的 Bug 和安全漏洞
2. 性能问题
3. 代码规范和可维护性
代码:
\`\`\`{language}
{code}
\`\`\`
请给出具体的改进建议。这样 Prompt 和代码分离,PM 改 Prompt 不用动代码。
多轮对话——记忆管理
默认 ChatClient 每次对话是无状态的。要做多轮对话,需要用 ChatMemory:
@Configuration
public class ChatConfig {
@Bean
public ChatMemory chatMemory() {
// 生产环境换成 Redis 或数据库实现
return new InMemoryChatMemory();
}
@Bean
public ChatClient chatClient(ChatClient.Builder builder, ChatMemory chatMemory) {
return builder
.defaultSystem("你是一个专业的技术助手。")
.defaultAdvisors(
new MessageChatMemoryAdvisor(chatMemory),
new SimpleLoggerAdvisor() // 开发时开启日志
)
.build();
}
}@RestController
@RequestMapping("/ai")
public class ConversationController {
private final ChatClient chatClient;
public ConversationController(ChatClient chatClient) {
this.chatClient = chatClient;
}
@PostMapping("/conversation")
public String conversation(
@RequestParam String message,
@RequestParam String conversationId) {
return chatClient.prompt()
.user(message)
.advisors(a -> a.param(ChatMemory.CONVERSATION_ID, conversationId))
.call()
.content();
}
}conversationId 用来区分不同用户/会话,可以用 UUID 或者 userId。
踩坑实录
坑一:Token 消耗超预期
现象:接入后账单暴增,一个简单问答消耗了 2000+ tokens。
原因:多轮对话默认把所有历史消息都发给模型,随着对话轮次增加,每次请求的 token 越来越多。实测一个 10 轮对话,最后一轮请求消耗了 3800 tokens,而实际新增信息只有 200 tokens。
解法:设置历史消息窗口大小。
@Bean
public ChatMemory chatMemory() {
return new InMemoryChatMemory();
}
// 配置只保留最近 10 条消息
new MessageChatMemoryAdvisor(chatMemory, DEFAULT_CONVERSATION_ID, 10)或者用 Token 数量来控制窗口(更精准):
// 历史消息不超过 2000 tokens
new TokenCountingChatMemoryAdvisor(chatMemory, tokenCountEstimator, 2000)坑二:超时配置没生效
现象:请求偶尔卡住超过 60 秒才超时,但我明明配了 30 秒超时。
原因:Spring AI 底层用 RestClient 或 WebClient,需要在 AutoConfiguration 之外单独配置 HTTP 超时。仅在 application.yml 里配超时对 HTTP 层不生效。
解法:
@Configuration
public class OpenAiConfig {
@Bean
public OpenAiApi openAiApi(
@Value("${spring.ai.openai.api-key}") String apiKey,
@Value("${spring.ai.openai.base-url}") String baseUrl) {
RestClient.Builder restClientBuilder = RestClient.builder()
.requestFactory(clientHttpRequestFactory());
return new OpenAiApi(baseUrl, apiKey, restClientBuilder);
}
private ClientHttpRequestFactory clientHttpRequestFactory() {
HttpComponentsClientHttpRequestFactory factory =
new HttpComponentsClientHttpRequestFactory();
factory.setConnectTimeout(Duration.ofSeconds(10));
factory.setReadTimeout(Duration.ofSeconds(30));
return factory;
}
}坑三:切换模型提供方时类型不兼容
现象:从 OpenAI 切换到 Azure OpenAI,改了 starter 依赖但报 Bean 注入失败。
原因:Azure OpenAI 的配置前缀是 spring.ai.azure.openai,而不是 spring.ai.openai,而且部分配置项名称不同(deployment-name 而不是 model)。
解法:换用抽象层 ChatModel 注入,而不是具体的 OpenAiChatModel:
// 错误方式
@Autowired
private OpenAiChatModel chatModel; // 换 provider 就挂
// 正确方式
@Autowired
private ChatModel chatModel; // 面向抽象,换 provider 无感结构化输出——直接拿 Java 对象
这个功能很实用,让模型直接返回可用的 Java 对象:
public record TechArticle(
String title,
List<String> keyPoints,
String targetAudience,
int estimatedReadingMinutes
) {}
@GetMapping("/generate-outline")
public TechArticle generateOutline(@RequestParam String topic) {
return chatClient.prompt()
.user("为主题「" + topic + "」生成一篇技术文章的大纲")
.call()
.entity(TechArticle.class);
}Spring AI 底层会自动生成 JSON Schema,注入到 Prompt 里,并把响应反序列化为对象。
实测成功率:gpt-4o 约 98%,gpt-4o-mini 约 93%,对于不复杂的结构基本够用。
生产部署注意事项
几个必须配的东西:
spring:
ai:
openai:
api-key: ${OPENAI_API_KEY} # 必须用环境变量,不能硬编码
chat:
options:
model: gpt-4o-mini
max-tokens: 2048
# 生产环境一定要设 max-tokens,防止账单爆炸
# 开启 Actuator 监控
management:
endpoints:
web:
exposure:
include: health,metrics,info限流保护(生产必须):
@Component
public class RateLimitAdvisor implements CallAroundAdvisor {
private final RateLimiter rateLimiter = RateLimiter.create(10.0); // 10 QPS
@Override
public AdvisedResponse aroundCall(AdvisedRequest request, CallAroundAdvisorChain chain) {
if (!rateLimiter.tryAcquire()) {
throw new TooManyRequestsException("AI 服务请求过于频繁,请稍后再试");
}
return chain.nextAroundCall(request);
}
@Override
public String getName() {
return "RateLimitAdvisor";
}
@Override
public int getOrder() {
return 0;
}
}选型对比:Spring AI vs 手撸 HTTP vs LangChain4j
| 维度 | Spring AI | 手撸 HTTP | LangChain4j |
|---|---|---|---|
| 上手难度 | 低(熟悉 Spring 即可) | 低(但细节多) | 中 |
| 功能完整性 | 高 | 需要自己实现所有高级功能 | 高 |
| 社区活跃度 | 高(Spring 官方) | - | 中 |
| 与 Spring Boot 集成 | 无缝 | 需要手动封装 | 需要适配 |
| 生产可用性 | 1.0 Release 可用 | 看实现质量 | GA 版可用 |
结论:如果你是 Spring 项目,直接用 Spring AI,没必要手撸。
Spring AI 的 Advisor 机制——切面增强
Advisor 是 Spring AI 最优雅的设计之一,类似 AOP,可以在每次调用前后插入逻辑。
内置的常用 Advisor:
MessageChatMemoryAdvisor:注入对话记忆,让模型记住历史对话。 QuestionAnswerAdvisor:注入 RAG 检索上下文,不需要自己写检索逻辑。 SimpleLoggerAdvisor:开发时开启,把每次请求和响应打印到日志,调试非常方便。 SafeGuardAdvisor:输入安全检查,过滤危险词汇。
自定义 Advisor 示例:
假设我要做一个请求审计的 Advisor,记录每次 AI 对话的用户、问题、回答到数据库:
@Component
public class AuditAdvisor implements CallAroundAdvisor {
private final AuditLogRepository auditLogRepository;
@Override
public AdvisedResponse aroundCall(AdvisedRequest request, CallAroundAdvisorChain chain) {
long startTime = System.currentTimeMillis();
String userId = (String) request.adviseContext().getOrDefault("userId", "anonymous");
// 执行实际调用
AdvisedResponse response = chain.nextAroundCall(request);
long elapsed = System.currentTimeMillis() - startTime;
// 异步写入审计日志(不影响主流程)
auditLogRepository.saveAsync(AuditLog.builder()
.userId(userId)
.question(extractUserMessage(request))
.answer(response.response().getResult().getOutput().getContent())
.latencyMs(elapsed)
.model(response.response().getMetadata().getModel())
.tokenUsage(response.response().getMetadata().getUsage().getTotalTokens())
.build());
return response;
}
@Override
public String getName() {
return "AuditAdvisor";
}
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE; // 最后执行,保证能拿到完整响应
}
}配合 userId 传入:
chatClient.prompt()
.user(message)
.advisors(a -> a.param("userId", currentUserId))
.call()
.content();这样每次 AI 对话都有完整的审计记录,方便排查问题、分析用户行为。
多模型切换与 Fallback
生产环境里,主模型可能偶尔不可用(限流、维护),需要备用模型:
@Configuration
public class MultiModelConfig {
@Bean
@Primary
public ChatModel primaryChatModel() {
return OpenAiChatModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.model("gpt-4o")
.build();
}
@Bean("fallbackModel")
public ChatModel fallbackChatModel() {
// 备用:DeepSeek,成本低
return OpenAiChatModel.builder()
.apiKey(System.getenv("DEEPSEEK_API_KEY"))
.baseUrl("https://api.deepseek.com")
.model("deepseek-chat")
.build();
}
}@Service
public class ResilientChatService {
private final ChatModel primaryModel;
private final ChatModel fallbackModel;
private final CircuitBreaker circuitBreaker;
public String chat(String message) {
try {
return circuitBreaker.executeSupplier(() ->
primaryModel.call(new Prompt(message)).getResult().getOutput().getContent()
);
} catch (Exception e) {
log.warn("主模型调用失败,切换备用模型: {}", e.getMessage());
return fallbackModel.call(new Prompt(message)).getResult().getOutput().getContent();
}
}
}这个 Fallback 机制很重要,尤其是高可用要求的系统。OpenAI 偶尔会有区域性不可用,这时候切到备用模型可以保证服务不中断。
本地开发调试技巧
几个让开发体验更好的配置:
1. 开发环境用 Ollama 省钱
在本地起一个 Ollama,开发调试时用本地模型,不花 API 费用,联调完了再换到生产模型:
# application-dev.yml(开发环境)
spring:
ai:
openai:
base-url: http://localhost:11434/v1 # Ollama 兼容 OpenAI API 格式
api-key: ollama # 随意写,Ollama 不验证
chat:
options:
model: qwen2.5:7b# application-prod.yml(生产环境)
spring:
ai:
openai:
base-url: https://api.openai.com
api-key: ${OPENAI_API_KEY}
chat:
options:
model: gpt-4o2. SimpleLoggerAdvisor 只在开发环境开启
@Bean
public ChatClient chatClient(
ChatClient.Builder builder,
@Value("${spring.profiles.active:prod}") String activeProfile) {
ChatClient.Builder b = builder.defaultSystem("你是专业技术助手");
// 开发环境才加日志 Advisor
if ("dev".equals(activeProfile) || "local".equals(activeProfile)) {
b.defaultAdvisors(new SimpleLoggerAdvisor());
}
return b.build();
}3. 录制/回放测试(Spring AI Test 模块)
@SpringBootTest
@ActiveProfiles("test")
class ChatServiceTest {
@Autowired
private ChatClient chatClient;
// Spring AI 提供 MockChatModel,可以预设响应
@Test
void testChatWithMockModel() {
// 使用 Spring AI Test 的 Mock 模式,不真实调用 API
// 测试业务逻辑,不依赖外部服务
}
}这些调试技巧能让你在开发阶段节省大量 API 费用和等待时间。
总结
Spring AI 对 Java 工程师非常友好,核心概念就那么几个:
ChatClient:发送对话请求的核心类Prompt / PromptTemplate:管理提示词ChatMemory:多轮对话记忆Advisor:切面增强(限流、日志、记忆等)entity():结构化输出
学 Spring AI 的最快方式是直接做一个完整的小项目:客服机器人、文档问答、代码审查工具,随便选一个你觉得有意思的,把这篇文章里的组件都用上。真正用过一次,比看十遍文档更有收获。
下一篇我会讲 Spring AI 的函数调用(Function Calling),让大模型能调用你的 Java 服务,这个才是真正让 AI 有实际能力的关键。
