前言
个人网站:lihainuo.com
代码仓库地址:github.com/leehainuo/s...
Spring AI 为 Java 程序员带来了开发AI应用的高效方式,小nuo这几天上手体验了感觉非常的不错,但是也发现了框架的不足,因为比较新吧,所以有一些功能还没实现,小nuo今天就是要写一篇文章,解决 Spring AI 持久化存储到 Redis!😎
构建可序列化的 Message 包装类
自定义 ChatMemory 并不难,难的就是Redis存储与读取时需要序列化与反序列化。 Spring AI 中的 Message 体系结构如下:

小nuo查看源码发现,SystemMessage
、UserMessage
等实现类均未实现 Serializable 接口,直接存储会导致序列化失败。解决方案是创建可序列化的包装类。
SerializableMessage
类核心代码如下:
java
// ...
/**
* 可序列化的 Message 包装类
*/
@Getter
public class SerializableMessage implements Serializable {
// 定义序列化版本号, 用于版本控制
private static final long serialVersionUID = 1L;
// 明确 Message 接口实现类的枚举
public enum MessageType { USER, ASSISTANT, SYSTEM }
private final MessageType messageType;
private final String textContent;
private final Map<String, Object> metadata;
public SerializableMessage(Message message) {
this.messageType = getMessageType(message);
this.textContent = message.getText();
this.metadata = message.getMetadata();
}
// 判断属于哪种具体的实现类的类型
private MessageType getMessageType(Message message) {
if (message instanceof SystemMessage) return MessageType.SYSTEM;
if (message instanceof UserMessage) return MessageType.USER;
if (message instanceof AssistantMessage) return MessageType.ASSISTANT;
throw new IllegalArgumentException("Unknown message type: " + message.getClass().getName());
}
// 将 SerializableMessage 转换为 Message
public Message toMessage() {
switch (messageType) {
case SYSTEM:
return new SystemMessage(textContent);
case ASSISTANT:
return new AssistantMessage(textContent, metadata);
case USER:
return new UserMessage(textContent);
default:
throw new IllegalArgumentException("Unknown message type: " + messageType);
}
}
// ...
}
✨小nuo的逻辑思路:
- 采用枚举明确定义消息类型,避免序列化时的类型丢失
- 保留核心字段:文本内容和元数据
- 提供双向转换方法,无缝对接原有 Message 体系
RedisTemplate 配置:关键在于序列化器
Spring Data Redis 的核心是 RedisTemplate,而序列化配置是实现分布式存储的关键。
首先引入Redis依赖:
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
配置配置文件连接Redis:
yaml
spring:
data:
redis:
host: localhost
port: 6379
模板配置
模板配置为难点,分如下四个步骤实现:
- 声明自定义的RedisTemplate<String, SerializableMessage>模板实例 注意:Value 的泛型指定 SerializableMessage 类型
- 配置支持多态的 Jackson 映射器 原因:多态场景下(如父类引用指向子类对象),序列化时若不存储类型信息,反序列化会丢失 子类型细节,导致数据还原失败(ClassCastException异常)
- 创建自定义序列化工具 注意:自定义序列化工具基于上一步的 Jackson 映射器去创建
- Key使用String序列化:Value使用自定义序列化工具
java
@Configuration
public class RedisConfig {
// 声明自定义的 RedisTemplate - Spring AI 序列化器
@Bean("AiRedisTemplate")
public RedisTemplate<String, SerializableMessage> AiRedisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, SerializableMessage> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
// 配置支持多态的 Jackson 序列化器
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.activateDefaultTyping( // 启用多态类型处理
objectMapper.getPolymorphicTypeValidator(), // 获取多态类型验证器
ObjectMapper.DefaultTyping.EVERYTHING, // 指定类型信息添加范围:所有类型(包括 final 类型)
JsonTypeInfo.As.PROPERTY // 指定类型信息添加方式:属性方式
);
// 创建自定义的序列化工具
GenericJackson2JsonRedisSerializer serializer =
new GenericJackson2JsonRedisSerializer(objectMapper);
// Key 使用 String 序列化:value - 使用自定义的序列化工具
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(serializer);
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(serializer);
return template;
}
}
实现 ChatMemory
自定义 ChatMemory 非常简单😎,通过查看源码可以看到实现 ChatMemory
需要实现其4个功能,我们只需要照着其中的 InMemoryChatMemory
重构功能,改造成使用 Redis 操作即可:
java
/**
* 自定义 Redis 存储 Memory
* todo: 需要将 Message -> SerializableMessage
*/
public class RedisChatMemory implements ChatMemory {
private final RedisTemplate<String, SerializableMessage> redisTemplate;
/**
* 构造器注入
* @param redisTemplate Redis 模板
*/
public RedisChatMemory(RedisTemplate<String, SerializableMessage> redisTemplate) {
this.redisTemplate = redisTemplate;
}
@Override
public void add(String conversationId, Message message) {
// 转换数据
SerializableMessage serializableMessage = new SerializableMessage(message);
// 向 Redis 列表中添加数据 左侧添加
redisTemplate.opsForList().leftPush(conversationId, serializableMessage);
}
@Override
public void add(String conversationId, List<Message> messages) {
// 转换数据
List<SerializableMessage> serializableMessages = messages.stream()
.map(SerializableMessage::new)
.collect(Collectors.toList());
// 向 Redis 列表中添加数据 左侧添加
if (serializableMessages != null && !serializableMessages.isEmpty()) {
redisTemplate.opsForList().leftPushAll(conversationId, serializableMessages);
}
}
@Override
public List<Message> get(String conversationId, int lastN) {
// 需要将 SerializableMessage -> Message
List<SerializableMessage> serializableMessages = redisTemplate.opsForList()
.range(conversationId, 0, lastN - 1);
// 处理空结果
if (serializableMessages == null || serializableMessages.isEmpty()) {
return List.of();
}
// 处理非空结果
return serializableMessages.stream()
.map(SerializableMessage::toMessage)
.collect(Collectors.toList());
}
@Override
public void clear(String conversationId) {
// 清空 Redis 列表
redisTemplate.delete(conversationId);
}
}
然后给 ChatClient 配置即可:
java
ChatMemory chatMemory = new RedisChatMemory(redisTemplate);
chatClient = ChatClient.builder(dashscopeChatModel)
.defaultSystem(SYSTEM_PROMPT)
.defaultAdvisors(
new MessageChatMemoryAdvisor(chatMemory),
// 自定义日志 Advisor 可按需开启
new CustomLoggerAdvisor()
// 自定义增强推理能力 Advisor 可按需开启
// new ReReadingAdvisor()
)
.build();
总结
至此就完成了 Redis的序列化难题,小nuo自己做了一个Starter供大家快捷使用与学习,如果能给小nuo一个star⭐真的万分感激!!!有什么疑问与更好的方法,小nuo欢迎大家在评论区一起讨论。