You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多
文档控制台
免费开始使用

使用Kryo框架序列化Java Collections.synchronizedMap反序列化异常问题

解决Kryo序列化Collections.synchronizedMap时反序列化的NullPointerException问题

你碰到的这个NPE问题其实很典型——Kryo默认的序列化逻辑对java.util.Collections$SynchronizedMap这种包装类的处理不够到位。虽然你已经注册了这个类,但Kryo只是简单实例化了它,却没有初始化它内部那个用来实际存储数据的私有map字段,所以反序列化后的实例本质上是个空壳,调用任何方法都会因为内部map为null抛出异常。

修复方案:自定义序列化器

要解决这个问题,我们需要为Collections$SynchronizedMap写一个自定义的Kryo序列化器,手动处理内部map的读写逻辑:

  1. 首先创建自定义序列化器类:
import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.Serializer;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;
import java.util.Collections;
import java.util.Map;
import java.lang.reflect.Field;

public class SynchronizedMapSerializer extends Serializer<Map> {
    @Override
    public void write(Kryo kryo, Output output, Map map) {
        try {
            // 通过反射获取SynchronizedMap内部的私有map字段
            Field innerMapField = Collections.SynchronizedMap.class.getDeclaredField("m");
            innerMapField.setAccessible(true);
            Map innerMap = (Map) innerMapField.get(map);
            // 序列化内部实际存储数据的map
            kryo.writeClassAndObject(output, innerMap);
        } catch (NoSuchFieldException | IllegalAccessException e) {
            throw new RuntimeException("Failed to serialize SynchronizedMap", e);
        }
    }

    @Override
    public Map read(Kryo kryo, Input input, Class<? extends Map> type) {
        // 先反序列化内部的map
        Map innerMap = (Map) kryo.readClassAndObject(input);
        // 重新包装成线程安全的SynchronizedMap
        return Collections.synchronizedMap(innerMap);
    }
}
  1. 修改你的Kryo注册代码,将自定义序列化器与Collections$SynchronizedMap绑定:
Serializer SERIALIZER = Serializer.using(KryoNamespace.builder()
        .register(KryoNamespaces.BASIC)
        // 注册类并关联自定义序列化器
        .register(Class.forName("java.util.Collections$SynchronizedMap"), new SynchronizedMapSerializer())
        .build());

// 测试代码保持不变
Map<String, String> map = Collections.synchronizedMap(new HashMap<String, String>());
map.put("test", "123");
byte[] encoded = SERIALIZER.encode(map);
Map<String, String> decoded = SERIALIZER.decode(encoded);

// 现在调用put等方法不会再抛出NPE了
decoded.put("newKey", "newValue");

为什么这个方案有效?

  • 序列化阶段:我们通过反射拿到SynchronizedMap内部的实际存储map,只序列化这个核心数据,避免了Kryo默认逻辑无法处理私有字段的问题。
  • 反序列化阶段:我们先恢复内部的普通map,再用Collections.synchronizedMap()重新包装成线程安全的实例,既保留了原有的线程安全特性,又保证了数据完整初始化。

内容的提问来源于stack exchange,提问作者Alexander Richter

火山引擎 最新活动