You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot中Kryo序列化非序列化动态对象存入Redis缓存报错求助

解决Kryo序列化Redis时的NotSerializableException问题

问题根源

你遇到的NotSerializableException并不是Kryo本身的限制,而是因为你配置了SerializingInstantiatorStrategy——这个实例化策略依赖Java原生的序列化机制,强制要求目标类实现Serializable接口,这和你使用Kryo的初衷完全冲突了。

另外你的Kryo配置还有两个隐性问题:

  • 没有关闭类注册要求:Kryo默认要求提前注册所有要序列化的类,但你的对象是动态未知类型,根本无法提前完成注册
  • 构造函数中直接操作ThreadLocal内的Kryo实例:存在线程安全风险,因为构造函数可能在多线程场景下被调用

修正后的Kryo序列化器实现

替换SerializingInstantiatorStrategy为Kryo默认的StdInstantiatorStrategy(如果你的动态类没有无参构造,还可以用更强大的ObjenesisStdInstantiatorStrategy),同时关闭类注册要求:

import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;
import com.esotericsoftware.kryo.util.DefaultInstantiatorStrategy;
import org.objenesis.strategy.StdInstantiatorStrategy;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;

import java.io.ByteArrayOutputStream;
import java.io.IOException;

public class KryoRedisSerializer implements RedisSerializer<Object> {

    // 用ThreadLocal保证Kryo实例的线程安全(Kryo本身非线程安全)
    private final ThreadLocal<Kryo> kryoThreadLocal = ThreadLocal.withInitial(() -> {
        Kryo kryo = new Kryo();
        // 关闭注册要求,支持动态未知类型
        kryo.setRegistrationRequired(false);
        // 使用StdInstantiatorStrategy,无需类实现Serializable接口
        kryo.setInstantiatorStrategy(new DefaultInstantiatorStrategy(new StdInstantiatorStrategy()));
        return kryo;
    });

    @Override
    public byte[] serialize(Object object) throws SerializationException {
        if (object == null) {
            return new byte[0];
        }
        try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
             Output output = new Output(outputStream)) {
            kryoThreadLocal.get().writeClassAndObject(output, object);
            return output.toBytes();
        } catch (IOException e) {
            throw new SerializationException("Failed to serialize object", e);
        }
    }

    @Override
    public Object deserialize(byte[] bytes) throws SerializationException {
        if (bytes == null || bytes.length == 0) {
            return null;
        }
        try (Input input = new Input(bytes)) {
            return kryoThreadLocal.get().readClassAndObject(input);
        }
    }
}

修正RedisTemplate配置

确保所有值序列化器都使用Kryo(包括Hash类型的值),同时修正代码中的笔误:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
public class SpringRedisConfig {

    @Bean
    public JedisConnectionFactory connectionFactory() {
        JedisConnectionFactory connectionFactory = new JedisConnectionFactory();
        connectionFactory.setHostName("localhost");
        connectionFactory.setPort(6379);
        return connectionFactory;
    }

    @Bean
    public RedisTemplate<String, Object> redisTemplate() {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(connectionFactory());
        
        RedisSerializer<Object> kryoSerializer = kryoRedisSerializer();
        
        // Key用字符串序列化,Value和HashValue用Kryo序列化
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(kryoSerializer);
        redisTemplate.setHashValueSerializer(kryoSerializer);
        
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }

    @Bean
    public RedisSerializer<Object> kryoRedisSerializer() {
        return new KryoRedisSerializer();
    }
}

修正主类的使用方式

不需要直接调用配置类的方法,直接注入Spring容器管理的RedisTemplate即可:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;

@SpringBootApplication
public class SampleRedisApplication implements CommandLineRunner {

    // 直接注入容器中已配置好的RedisTemplate
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Override
    public void run(String... args) throws Exception {
        ValueOperations<String, Object> values = redisTemplate.opsForValue();
        try {
            // 模拟从远程API获取的动态类型对象
            Student student = new Student(1L, "Vishal");
            values.set("student", student);
            Student std = (Student) values.get("student");
            System.out.println(std.getId());
            System.out.println(std.getName());
        } catch(Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws Exception {
        SpringApplication.run(SampleRedisApplication.class, args).close();
    }

    // 模拟动态获取的POJO,无需实现Serializable接口
    static class Student {
        private Long id;
        private String name;

        public Student(Long id, String name) {
            this.id = id;
            this.name = name;
        }

        public Long getId() { return id; }
        public String getName() { return name; }
    }
}

关键说明

  • StdInstantiatorStrategy通过反射直接创建对象实例,不需要类实现Serializable,也不强制要求有无参构造函数
  • kryo.setRegistrationRequired(false)允许Kryo序列化未提前注册的类,完美适配你的动态类型场景
  • 使用ThreadLocal管理Kryo实例,彻底避免Kryo非线程安全带来的问题

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 03:41:41