1 篇文章

Kryo

Kryo序列化问题
 • 分类:java • 标签:Kryo

深藏不露的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();
        }
    }
}

然而,系统在运行中突然抛出异常:

阅读更多 »