深藏不露的Bug:当Kryo序列化遇上toString()
在软件开发中,我们时常与各种Bug不期而遇。有些Bug显而易见,错误日志直指病灶;而另一些则如冰山一角,表面现象可能误导我们偏离真正的根源。
本文将详细复盘一个在Java项目中,因错误处理Kryo序列化字节流而导致的Bug: ClassNotFoundException
,揭示一个使用Kryo序列化后又使用 toString()
导致的Bug。
项目背景和问题分析
AI工具日趋强大,为了熟悉Spring AI和体验现在AI编程工具的潜力,我选择了使用AI来辅助我开发一个基于Spring AI的项目。
一个令人困惑的Bug
在使用Spring AI构建聊天功能时,实现对话记忆(ChatMemory
)时计划采用Kryo进行序列化,并存储于Redis中。
于是我在IDEA中通过 AI 插件实现了 RedisChatMemory
类,以实现基于Redis的AI对话记忆功能。代码如下:
java
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.messages.Message;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.io.Output;
import com.esotericsoftware.kryo.io.Input;
import org.objenesis.strategy.StdInstantiatorStrategy;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.util.ArrayList;
import java.util.List;
@Component
public class RedisChatMemory implements ChatMemory {
private final StringRedisTemplate redisTemplate;
private static final Kryo kryo = new Kryo();
static {
kryo.setRegistrationRequired(false);
// 设置实例化策略
kryo.setInstantiatorStrategy(new StdInstantiatorStrategy());
}
@Autowired
public RedisChatMemory(StringRedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
}
@Override
public void add(String conversationId, List<Message> messages) {
List<Message> conversationMessages = getOrCreateConversation(conversationId);
conversationMessages.addAll(messages);
saveConversation(conversationId, conversationMessages);
}
@Override
public List<Message> get(String conversationId, int lastN) {
List<Message> allMessages = getOrCreateConversation(conversationId);
return allMessages.stream()
.skip(Math.max(0, allMessages.size() - lastN))
.toList();
}
@Override
public void clear(String conversationId) {
redisTemplate.delete(conversationId);
}
private List<Message> getOrCreateConversation(String conversationId) {
String data = redisTemplate.opsForValue().get(conversationId);
if (data != null && !data.isEmpty()) {
try (Input input = new Input(new ByteArrayInputStream(data.getBytes()))) {
return kryo.readObject(input, ArrayList.class);
} catch (Exception e) {
e.printStackTrace();
}
}
return new ArrayList<>();
}
private void saveConversation(String conversationId, List<Message> messages) {
try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
Output output = new Output(bos)) {
kryo.writeObject(output, messages);
output.flush();
redisTemplate.opsForValue().set(conversationId, bos.toString());
} catch (Exception e) {
e.printStackTrace();
}
}
}
然而,系统在运行中突然抛出异常: