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
相关产品推荐
相关产品推荐

