前言
在构建智能对话应用时,多轮对话的上下文记忆能力是刚需。Spring AI Alibaba 提供了一套优雅的 ChatMemory 机制,将对话历史的管理、存储和恢复过程封装得极为简洁。本文将带大家从零掌握如何用 Spring AI Alibaba 集成 ChatMemory,涵盖内存存储、Redis 持久化、流式 SSE 输出等场景,并给出可直接用于生产的配置方案。
一、架构流程与核心组件
整个记忆链路如下:
前端请求 → ChatClient → MessageChatMemoryAdvisor(记忆拦截器)
↓ 自动读取/写入会话历史
ChatMemory(消息窗口策略:滑动窗口,限制消息条数)
↓ 底层存储
ChatMemoryRepository(存储层:内存/Redis/MySQL)
↓ 底层调用
DashScopeChatModel → DashScopeApi → 阿里灵积 DashScope 平台
1. ChatMemoryRepository 存储仓库
负责对话消息持久化存储,Spring AI Alibaba 内置三种实现:
- InMemoryChatMemoryRepository:内存存储,单机测试专用,重启丢失数据
- RedisChatMemoryRepository:Redis 持久化,集群/生产推荐,会话自动过期,支持两种构建模式:Jedis 直连、复用 Spring Redis 连接工厂
- JdbcChatMemoryRepository:数据库持久化,长期留存对话记录
2. ChatMemory 记忆策略层
标准实现:MessageWindowChatMemory,滑动窗口淘汰机制,配置maxMessages限制最大保存消息条数,自动丢弃最早对话,防止上下文 Token 超限、API 计费暴涨。
3. MessageChatMemoryAdvisor 记忆拦截器
挂载在 ChatClient 的 Advisor 链路中,请求前自动根据conversationId加载历史消息,模型返回后自动保存用户/助手消息,业务代码无需手动拼接历史。
4. ConversationId 会话唯一标识
不同用户、不同聊天窗口分配独立 ID,实现会话隔离,同一个 ID 自动复用历史对话,不同 ID 完全互不干扰。
5. ChatModel vs ChatClient 记忆能力区分
- ChatModel:底层原始接口,无内置记忆管理能力,需要手动拼接消息列表,代码冗余;
- ChatClient:上层封装,搭配 Advisor 一键集成 ChatMemory,支持全局系统提示词、消息自动管理、流式、函数调用,企业开发首选。
二、Maven 核心依赖与配置
2.1 基础依赖
<!-- 基础 DashScope 对话依赖(必引) -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-alibaba-starter-dashscope</artifactId>
</dependency>
内存存储无需额外依赖;Redis 持久化需引入记忆扩展包,并根据连接模式补充对应依赖:
<!-- Redis记忆扩展包,两种模式都必须引入 -->
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-starter-memory-redis</artifactId>
</dependency>
<!-- 方式A:Jedis直连模式,需要该依赖 -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
<!-- 方式B:复用Spring Redis连接工厂模式,需要该依赖 -->
<!--
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
-->
2.2 application.yml 基础配置
spring:
ai:
dashscope:
api-key: sk-xxxxxxxxxxxxxxxxxxxx
chat:
options:
model: qwen-turbo
temperature: 0.7
# Redis配置,Jedis直连模式通过@Value读取;连接池复用模式由SpringDataRedis自动读取
data:
redis:
host: 127.0.0.1
port: 6379
password: ""
database: 0
三、方案1:内存版 ChatMemory(本地测试首选)
内存实现适用于开发调试,提供两种写法:极简自动装配与手动配置定制窗口大小。
3.1 方式A:极简自动装配(90% 测试场景,零配置类)
Spring AI Alibaba 自动配置了InMemoryChatMemoryRepository,我们只需通过@Bean定义ChatMemory和ChatClient即可,无需手动创建仓库。
配置类:
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.memory.InMemoryChatMemoryRepository;
import org.springframework.ai.chat.memory.MessageWindowChatMemory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AiMemoryConfig {
// 使用自动装配的 InMemoryChatMemoryRepository
@Bean
public ChatMemory chatMemory(InMemoryChatMemoryRepository repository) {
return MessageWindowChatMemory.builder()
.chatMemoryRepository(repository)
.maxMessages(10) // 最多保存10条消息(5轮对话)
.build();
}
@Bean
public ChatClient chatClient(ChatModel chatModel, ChatMemory chatMemory) {
return ChatClient.builder(chatModel)
.defaultSystem("你是资深Java后端专家,结合上下文简洁回答问题")
.defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory).build())
.build();
}
}
Controller 示例:
@RestController
@RequestMapping("/ai/chat")
public class MemoryChatController {
@Autowired
private ChatClient chatClient;
@Autowired
private ChatMemory chatMemory;
/**
* 同步多轮对话
* @param conversationId 会话ID,前端生成UUID
* @param userMsg 用户提问
*/
@GetMapping("/sync")
public String syncChat(
@RequestParam String conversationId,
@RequestParam String userMsg) {
return chatClient.prompt()
.user(userMsg)
.advisors(spec -> spec.param(ChatMemory.CONVERSATION_ID, conversationId))
.call()
.content();
}
// 清空指定会话记忆
@DeleteMapping("/memory/clear")
public String clearMemory(@RequestParam String conversationId) {
chatMemory.clear(conversationId);
return "会话[" + conversationId + "]记忆已清空";
}
}
3.2 方式B:手动配置类(自定义窗口上限,推荐工程化写法)
若需完全掌控 Bean 创建过程,可手动声明 Repository 和 ChatMemory:
@Configuration
public class AiMemoryConfig {
@Bean
public InMemoryChatMemoryRepository memoryRepository() {
return new InMemoryChatMemoryRepository();
}
@Bean
public ChatMemory chatMemory(InMemoryChatMemoryRepository repository) {
return MessageWindowChatMemory.builder()
.chatMemoryRepository(repository)
.maxMessages(20) // 自定义窗口大小
.build();
}
@Bean
public ChatClient chatClient(ChatModel chatModel, ChatMemory chatMemory) {
return ChatClient.builder(chatModel)
.defaultSystem("你是资深Java后端专家,结合上下文简洁回答问题")
.defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory).build())
.build();
}
}
四、方案2:Redis 持久化 ChatMemory(生产环境集群推荐)
内存存储致命缺陷:服务重启、多实例集群时会话记忆丢失,线上必须使用 Redis 持久化。RedisChatMemoryRepository提供两套构建方式:
4.1 方式A:Jedis 直连(简单 Demo)
组件内部自建 Jedis 连接,不共享项目 Redis 连接池,yml 配置通过@Value读取。
@Configuration
public class RedisMemoryConfig {
@Value("${spring.data.redis.host}")
private String host;
@Value("${spring.data.redis.port}")
private int port;
@Value("${spring.data.redis.password:}")
private String password;
@Value("${spring.data.redis.database:0}")
private int database;
@Bean
public RedisChatMemoryRepository redisChatMemoryRepository() {
return RedisChatMemoryRepository.builder()
.host(host)
.port(port)
.password(password)
.database(database)
.build();
}
@Bean
public ChatMemory chatMemory(RedisChatMemoryRepository redisChatMemoryRepository) {
return MessageWindowChatMemory.builder()
.chatMemoryRepository(redisChatMemoryRepository)
.maxMessages(20)
.build();
}
@Bean
public ChatClient chatClient(ChatModel chatModel, ChatMemory chatMemory) {
return ChatClient.builder(chatModel)
.defaultSystem("你是通义千问对话助手,记住用户全部历史提问")
.defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory).build())
.build();
}
}
4.2 方式B:复用 Spring Data Redis 连接工厂(生产推荐)
项目已引入spring-boot-starter-data-redis时使用,连接池、哨兵、集群配置统一管理。
@Configuration
public class RedisMemoryConfig {
@Bean
public RedisChatMemoryRepository redisChatMemoryRepository(RedisConnectionFactory redisConnectionFactory) {
return RedisChatMemoryRepository.builder()
.redisConnectionFactory(redisConnectionFactory)
.build();
}
@Bean
public ChatMemory chatMemory(RedisChatMemoryRepository redisChatMemoryRepository) {
return MessageWindowChatMemory.builder()
.chatMemoryRepository(redisChatMemoryRepository)
.maxMessages(20)
.build();
}
@Bean
public ChatClient chatClient(ChatModel chatModel, ChatMemory chatMemory) {
return ChatClient.builder(chatModel)
.defaultSystem("你是通义千问对话助手,记住用户全部历史提问")
.defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory).build())
.build();
}
}
Redis 记忆特性:
- 会话数据序列化存入 Redis,服务重启不丢失;
- 支持配置 Key 过期时间,自动清理长期未使用会话;
- 微服务多实例集群共享 Redis,多节点对话上下文统一。
重要区分:
- Jedis 直连模式:内部新建独立 Jedis 连接,不和业务 Redis 共用连接池,适合简单 Demo;
- RedisConnectionFactory 模式:复用项目已有 Redis 连接池,适合正式业务系统。
五、流式 SSE 接口实现带记忆打字机效果
结合 ChatMemory 实现带历史上下文的实时流式输出,基于 WebFlux Flux + SSE:
@RestController
@RequestMapping("/ai/chat")
public class StreamMemoryChatController {
@Autowired
private ChatClient chatClient;
/**
* 流式带记忆对话,SSE实时推送
*/
@GetMapping(value = "/stream", produces = "text/event-stream;charset=utf-8")
public Flux<String> streamChatWithMemory(
@RequestParam String conversationId,
@RequestParam String userMsg) {
return chatClient.prompt()
.user(userMsg)
.advisors(spec -> spec.param(ChatMemory.CONVERSATION_ID, conversationId))
.stream()
.content();
}
}
六、总结与选型建议
- 开发测试:使用内存版
InMemoryChatMemoryRepository,极简配置,快速验证多轮对话逻辑。 - 单机生产:内存版不持久化,若可接受重启丢失历史,也可用;否则切换至 Redis 持久化。
- 集群/微服务:必须选择Redis 持久化,并推荐复用 Spring Data Redis 连接工厂,实现连接池统一管理,保证多实例会话共享。
- 流式输出:只需在
ChatClient基础上调用.stream().content()即可获得 SSE 流,记忆配置无需额外改动。
Spring AI Alibaba 的 ChatMemory 体系将对话上下文管理复杂度降到最低,配合 ChatClient 的 Advisor 机制,让我们能更专注于业务逻辑。希望本文能帮助大家快速落地多轮对话功能,如有疑问欢迎在评论区交流。