解决JDK 序列化导致的 Redis Key 非预期编码问题
@RequestMapping("/testList")
@ResponseBody
public String testList() {
//清楚之前的数据
redisTemplate.execute((RedisConnection redisConnection)->{
//excute 要求回调方法中必须写 return语句,返回个东西
//这个回调返回的对象,就会作为excute本身的返回值。
redisConnection.flushAll();
return null;
});
redisTemplate.opsForList().leftPush("key", "111");
redisTemplate.opsForList().leftPush("key", "222");
redisTemplate.opsForList().leftPush("key", "333");
String value = (String) redisTemplate.opsForList().leftPop("key");
System.out.println(value);
value = (String) redisTemplate.opsForList().leftPop("key");
System.out.println(value);
return "ok";
}
以上为List类型操作测试代码,没有什么问题。
浏览器正常响应
后端控制台也没有报错
问题产生原因:
但是却在xshell终端中出现了,key值存储显示异常的问题,同时单纯的对“key”使用命令,没有效果。之后我查找相关资料,了解到这是JDK 序列化导致的 Redis Key 非预期编码问题。产生这个问题的主要原因是RedisTemplate
默认使用JDK序列(JdkSerializationRedisSerializer
)作为 keySerializer
,它会把 Key 进行 Java 对象序列化后存入 Redis,而不是存储原始字符串。
问题解决办法:
优化默认的序列化方式:
Key 统一使用 StringRedisSerializer,避免 JDK 序列化的乱码问题。
Value 根据类型动态选择合适的序列化方式:
String 直接存 StringRedisSerializer。
对象、集合 存 JSON(GenericJackson2JsonRedisSerializer)。
避免 JSON 额外的 \",保证字符串存储不会被 JSON 影响。
支持 Java 8 时间类型的 JSON 序列化。
package org.example.redisdemo.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.BasicPolymorphicTypeValidator;
import com.fasterxml.jackson.databind.jsontype.PolymorphicTypeValidator;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
// 1. Key 和 HashKey 统一用 StringRedisSerializer
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
template.setKeySerializer(stringRedisSerializer);
template.setHashKeySerializer(stringRedisSerializer);
// 2. 配置 JSON 序列化器
ObjectMapper objectMapper = new ObjectMapper();
PolymorphicTypeValidator ptv = BasicPolymorphicTypeValidator.builder()
.allowIfSubType(Object.class)
.build();
objectMapper.activateDefaultTyping(ptv, ObjectMapper.DefaultTyping.NON_FINAL);
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
objectMapper.registerModule(new JavaTimeModule());
GenericJackson2JsonRedisSerializer jsonRedisSerializer = new GenericJackson2JsonRedisSerializer(objectMapper);
// 3. Value 序列化:String 类型的值用 StringRedisSerializer,其他用 JSON
template.setValueSerializer(new RedisSerializer<>() {
private final StringRedisSerializer stringSerializer = new StringRedisSerializer();
@Override
public byte[] serialize(Object o) {
if (o instanceof String) {
return stringSerializer.serialize((String) o);
}
return jsonRedisSerializer.serialize(o);
}
@Override
public Object deserialize(byte[] bytes) {
if (bytes == null) return null;
String str = stringSerializer.deserialize(bytes);
return (str != null && str.startsWith("\"") && str.endsWith("\"")) ? jsonRedisSerializer.deserialize(bytes) : str;
}
});
// 4. Hash Value 也做类似处理,避免 String 额外的 `\"`
template.setHashValueSerializer(new RedisSerializer<>() {
private final StringRedisSerializer stringSerializer = new StringRedisSerializer();
@Override
public byte[] serialize(Object o) {
if (o instanceof String) {
return stringSerializer.serialize((String) o);
}
return jsonRedisSerializer.serialize(o);
}
@Override
public Object deserialize(byte[] bytes) {
if (bytes == null) return null;
String str = stringSerializer.deserialize(bytes);
return (str != null && str.startsWith("\"") && str.endsWith("\"")) ? jsonRedisSerializer.deserialize(bytes) : str;
}
});
template.afterPropertiesSet();
return template;
}
}