一、前言
实操中使用SpringBoot操作Redis后,经常看到类似这样的内容:
127.0.0.1:6379> KEYS * 1) "\xac\xed\x00\x05t\x00\x11captcha:abc123"
127.0.0.1:6379> GET "\xac\xed\x00\x05t\x00\x11captcha:abc123" "\xac\xed\x00\x05t\x00\x04a1b2"
研究了一下,大体上是Redis本身只存字节数组,不存在乱码的情况,乱码是出在客户端写入前的序列化环节。
SpringBoot 提供的 RedisTemplate 默认使用 JDK 原生序列化(JdkSerializationRedisSerializer)。这种序列化方式是给 Java 对象在 Java 进程之间传输用的二进制协议,序列化结果会带上类信息、句柄等元数据字节,所以:
- 写入的字符串前后会附加
\xac\xed\x00\x05这样的序列化头; - 对象被压成二进制流造成不可读。
所以乱码不是Redis的问题,而是序列化器的问题。解决思路就是换掉默认的JDK序列化,key 用纯字符串序列化,value 用 JSON 序列化。
二、定制 RedisTemplate
2.1 常用序列化器
Spring Data Redis 提供了几种常用的序列化器,先有个整体认识:
| 序列化器 | 序列化结果 | 特点 |
|---|---|---|
JdkSerializationRedisSerializer | 二进制字节(默认) | 可读性差,跨语言不友好,但能还原任意 Serializable 对象 |
StringRedisSerializer | 纯字符串 | 完全可读,只适合存字符串 |
Jackson2JsonRedisSerializer | JSON | 可读性好,能绑定指定类型还原对象 |
GenericJackson2JsonRedisSerializer | JSON(带 @class 类型字段) | 可读性好,存任意对象都能自动还原类型 |
2.2 编写配置类
新建一个配置类,替换 RedisTemplate 的序列化规则:
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
// 把对象类型信息写入 JSON(@class 字段),读回来才能还原成原对象
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer =
new Jackson2JsonRedisSerializer(om, Object.class);
template.setConnectionFactory(factory);
RedisSerializer stringSerializer = new StringRedisSerializer();
// key 和 hashKey 用字符串序列化
template.setKeySerializer(stringSerializer);
template.setHashKeySerializer(stringSerializer);
// value 和 hashValue 用 JSON 序列化
template.setValueSerializer(jackson2JsonRedisSerializer);
template.setHashValueSerializer(jackson2JsonRedisSerializer);
return template;
}
}
template.setKeySerializer(...):设置普通 key 的序列化规则;template.setHashKeySerializer(...)/template.setHashValueSerializer(...):Hash 结构的 field 和 value 有自己独立的序列化器,必须单独设置。只设置 key 和 value、漏掉 hash 的两个,是常见的翻车点——明明配置了序列化器,opsForHash()写出来的数据照样乱码;enableDefaultTyping(NON_FINAL):往 JSON 里额外写入@class字段记录对象的完整类名。没有它,Object.class反序列化时不知道该还原成哪个类型,取出来会变成LinkedHashMap。
2.3 效果对比
重启项目后再次写入数据,redis-cli 里的效果:
127.0.0.1:6379> KEYS *
1) "captcha:abc123"
127.0.0.1:6379> GET captcha:abc123
"\"a1b2\""
127.0.0.1:6379> HGETALL user:1
1) "name"
2) "\"\xe5\xbc\xa0\xe4\xb8\x89\"" # UTF-8 编码的"张三",加 --no-raw 显示中文
三、@Cacheable 注解缓存
配完 RedisTemplate,发现 @Cacheable 注解生成的缓存依然是乱码,原因是注解缓存不走自定义的 RedisTemplate,而是走 Spring Cache 的 RedisCacheManager。它有一套独立的序列化配置,默认同样使用 JDK 序列化,两套配置互不相干。
3.1 定制 RedisCacheConfiguration
@EnableConfigurationProperties(CacheProperties.class)
@Configuration
@EnableCaching
public class MyCacheConfig {
@Bean
public RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
// 缓存 key 用字符串序列化
config = config.serializeKeysWith(
RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()));
// 缓存 value 用 JSON 序列化
config = config.serializeValuesWith(
RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
// 让 yaml 里 spring.cache.redis.* 的配置(TTL、key 前缀、空值缓存)生效
CacheProperties.Redis redisProperties = cacheProperties.getRedis();
if (redisProperties.getTimeToLive() != null) {
config = config.entryTtl(redisProperties.getTimeToLive());
}
if (redisProperties.getKeyPrefix() != null) {
config = config.prefixCacheNameWith(redisProperties.getKeyPrefix());
}
if (!redisProperties.isCacheNullValues()) {
config = config.disableCachingNullValues();
}
if (!redisProperties.isUseKeyPrefix()) {
config = config.disableKeyPrefix();
}
return config;
}
}
这里的 value 选用 GenericJackson2JsonRedisSerializer 而不是 Jackson2JsonRedisSerializer:注解缓存存的对象类型不固定(可能是 Product、可能是 List),Generic 版本会自动写入并识别 @class 字段。
@EnableConfigurationProperties(CacheProperties.class):把 yaml 中 spring.cache. 的配置读取进来,逐项应用到 RedisCacheConfiguration 上。Spring Boot 检测到这个 Bean 后会自动用它装配 RedisCacheManager,不需要再手动声明 CacheManager。
3.2 yaml 配置
spring:
cache:
type: redis
redis:
time-to-live: 3600000 # 缓存过期时间,单位毫秒
key-prefix: "cache:" # key 前缀
use-key-prefix: true
cache-null-values: true # 缓存空值,防止缓存穿透
配置完成后,@Cacheable(value = "productCache", key = "#id") 生成的 key 形如 cache:productCache::1,value 是带 @class 的 JSON。

暂无评论内容