深入解析Spring Boot中RedisTemplate的序列化机制与JSON存储
在昨天的文章(SpringBoot 响应 json 数据的后端源码处理流程)中,有人留言说,让我唠叨唠叨 SpringBoot 中 redis 的序列化器。

redis 缓存序列化,用的人很多,但是很少有人关注底层的细节,基于此,我就在写一个姊妹篇,聊聊 SpringBoot 中 redis 相关的序列化知识点。
插播一条 AD,阮一峰老师也推荐过的 Hulu AI,即将涨价。
在 SpringBoot 中,RedisTemplate 比较常用。默认使用 JdkSerializationRedisSerializer 来进行序列化。但是默认的序列化会有个问题:Java 端存完了,取 redis-cli 执行key *,会发现键值都带有\\xAC\\xED\\x00\\x05t\\x00\\x05这种字符,不方便查看,而且这如果在集群环境下会导致问题!
下面,我们就从 RedisTemplate 聊起吧。
RedisTemplate
可以看到 4 个序列化相关的属性 ,主要是用于KEY和VALUE的序列化,比如说我们经常会将 POJO 对象存储到Redis中,一般情况下会使用JSON方式序列化成字符串存储到Redis中 。
SpringBoot 提供的 Redis 数据结构的操作类。
ValueOperations 类,提供 Redis String API 操作
ListOperations 类,提供 Redis List API 操作
SetOperations 类,提供 Redis Set API 操作
ZSetOperations 类,提供 Redis ZSet(Sorted Set) API 操作
GeoOperations 类,提供 Redis Geo API 操作
HyperLogLogOperations 类,提供 Redis HyperLogLog API 操作
StringRedisTemplate
RedisTemplate 支持泛型,StringRedisTemplate K/V 均为 String 类型。
org.springframework.data.redis.core.StringRedisTemplate 继承 RedisTemplate 类,使用 org.springframework.data.redis.serializer.StringRedisSerializer字符串序列化方式。
RedisSerializer序列化接口
RedisSerializer接口是 Redis 序列化接口,用于 Redis KEY 和 VALUE 的序列化。
RedisSerializer 接口的实现类如下:
默认为 Redis 提供了 11 中的序列化方式,归类一下主要分为:
JDK序列化方式(默认)
String序列化方式
JSON序列化方式
XML序列化方式
JDK序列化方式(默认)
org.springframework.data.redis.serializer.JdkSerializationRedisSerializer,默认不配置的情况 RedisTemplate 采用的是该数据序列化方式,可以查看一下源码:
SpringBoot 自动化配置 RedisTemplate Bean 对象时,就未设置默认的序列化方式。绝大多数情况下,并不推荐使用 JdkSerializationRedisSerializer 进行序列化。主要是不方便人工排查数据。我们来做个测试:
运行单元测试:
发现 key 跟 value 的值都是 16 进制字符串,可以看到 key 跟 value 实际上保存的都是以byte[]字节数组的格式存储:
key 被序列化成这样,线上通过 key 去查询对应的 value 非常不方便,所以 key 肯定是不能被这样序列化的。value 被序列化成这样,除了阅读可能困难一点,不支持跨语言外,实际上也没多大问题。不过,实际线上场景,还是使用 JSON 序列化居多。
String序列化方式
org.springframework.data.redis.serializer.StringRedisSerializer,字符串和二进制数组都直接转换:
默认的话,StringRedisTemplate的 key 和 value 采用的就是这种序列化方案。
JSON序列话方式
GenericJackson2JsonRedisSerializer
org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer 使用 Jackson 实现 JSON 的序列化方式,Generic 单词翻译过来表示:通用的意思,可以看出,是支持所有类。
RedisConfig配置
通过配置方式选择对应 Redis 数据的序列化方式,配置如下:
package com.example.redis.serializer.config;
import com.alibaba.fastjson.support.spring.FastJsonRedisSerializer;
import com.alibaba.fastjson.support.spring.GenericFastJsonRedisSerializer;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
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.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/**
* Redis配置
*/
@Configuration
public class RedisConfig {
//GenericJackson2JsonRedisSerializer
@Bean
@ConditionalOnMissingBean(name = "redisTemplate")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory){
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
//String的序列化方式
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
// 使用GenericJackson2JsonRedisSerializer 替换默认序列化(默认采用的是JDK序列化)
GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
//key序列化方式采用String类型
template.setKeySerializer(stringRedisSerializer);
//value序列化方式采用jackson类型
template.setValueSerializer(genericJackson2JsonRedisSerializer);
//hash的key序列化方式也是采用String类型
template.setHashKeySerializer(stringRedisSerializer);
//hash的value也是采用jackson类型
template.setHashValueSerializer(genericJackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
//Jackson2JsonRedisSerializer
//@Bean
//@ConditionalOnMissingBean(name = "redisTemplate")
//public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
// RedisTemplate<String, Object> template = new RedisTemplate<>();
// template.setConnectionFactory(factory);
//
// //String的序列化方式
// StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
// //使用Jackson2JsonRedisSerialize 替换默认序列化(默认采用的是JDK序列化)
// Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
//
// // 如果采用Jackson2JsonRedisSerializer序列化方式,没有ObjectMapper配置在强转对象的时候会反序列化失败,也就是User user = (User) redisTemplate.opsForValue().get(key) 会失败;
// ObjectMapper objectMapper = new ObjectMapper();
// objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
// objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
// jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
//
// //key序列化方式采用String类型
// template.setKeySerializer(stringRedisSerializer);
// //value序列化方式采用jackson类型
// template.setValueSerializer(jackson2JsonRedisSerializer);
// //hash的key序列化方式也是采用String类型
// template.setHashKeySerializer(stringRedisSerializer);
// //hash的value也是采用jackson类型
// template.setHashValueSerializer(jackson2JsonRedisSerializer);
// template.afterPropertiesSet();
// return template;
//}
////FastJsonRedisSerializer
//@Bean("redisTemplate")
//public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory){
// RedisTemplate<String, Object> template = new RedisTemplate<>();
// template.setConnectionFactory(factory);
//
// //String序列化方式
// StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
// // 使用FastJsonRedisSerializer替换默认序列化(默认采用的是JDK序列化)
// FastJsonRedisSerializer<Object> fastJsonRedisSerializer = new FastJsonRedisSerializer<>(Object.class);
//
// //key序列化方式采用String类型
// template.setKeySerializer(stringRedisSerializer);
// //value序列化方式采用jackson类型
// template.setValueSerializer(fastJsonRedisSerializer);
// //hash的key序列化方式也是采用String类型
// template.setHashKeySerializer(stringRedisSerializer);
// //hash的value也是采用jackson类型
// template.setHashValueSerializer(fastJsonRedisSerializer);
// template.afterPropertiesSet();
// return template;
//}
}
运行以下测试类:
@Test
void redisTemplateSerializeTest() {
String redisTemplateStringKey = "redisTemplateStringKey";
String redisTemplateUserObjectKey = "redisTemplateUserObjectKey";
String redisTemplateUserArrayObjectKey = "redisTemplateUserArrayObjectKey";
String redisTemplateJSONObjectKey = "redisTemplateJSONObjectKey";
String redisTemplateJSONArrayKey = "redisTemplateJSONArrayKey";
//序列化String类型和反序列化String类型
redisTemplate.opsForValue().set(redisTemplateStringKey, "austin");
String austin = (String) redisTemplate.opsForValue().get(redisTemplateStringKey);
System.out.println("stringGet: " + austin);
//序列化Object对象类型和反序列化Object对象类型 (User对象)
User user = new User("123", "austin", 25);
redisTemplate.opsForValue().set(redisTemplateUserObjectKey, user);
User userGet = (User) redisTemplate.opsForValue().get(redisTemplateUserObjectKey);
System.out.println("userGet: " + userGet);
//序列化Object对象数组类型和反序列化Object对象数组类型 (User[]对象数组)
User user1 = new User("1", "austin1", 25);
User user2 = new User("2", "austin2", 25);
User[] userArray = new User[]{user1, user2};
redisTemplate.opsForValue().set(redisTemplateUserArrayObjectKey, userArray);
User[] userArrayGet = (User[]) redisTemplate.opsForValue().get(redisTemplateUserArrayObjectKey);
System.out.println("userArrayGet: " + userArrayGet);
//序列化JSONObject对象类型和反序列化JSONObject对象类型
JSONObject jsonObject = new JSONObject();
jsonObject.put("id", "123");
jsonObject.put("name", "austin");
jsonObject.put("age", 25);
redisTemplate.opsForValue().set(redisTemplateJSONObjectKey, jsonObject);
JSONObject jsonObjectGet = (JSONObject) redisTemplate.opsForValue().get(redisTemplateJSONObjectKey);
System.out.println("jsonObjectGet: " + jsonObjectGet);
//序列化JSONArray对象类型和反序列化JSONArray对象类型
JSONArray jsonArray = new JSONArray();
JSONObject jsonObject1 = new JSONObject();
jsonObject1.put("id", "1");
jsonObject1.put("name", "austin1");
jsonObject1.put("age", 25);
JSONObject jsonObject2 = new JSONObject();
jsonObject2.put("id", "1");
jsonObject2.put("name", "austin2");
jsonObject2.put("age", 25);
jsonArray.add(jsonObject1);
jsonArray.add(jsonObject2);
redisTemplate.opsForValue().set(redisTemplateJSONArrayKey, jsonArray);
JSONArray jsonArrayGet = (JSONArray) redisTemplate.opsForValue().get(redisTemplateJSONArrayKey);
System.out.println("jsonArrayGet: " + jsonArrayGet);
}
观察 redis 数据的存储格式:
key- value :
字符串类型
Key: redisTemplateStringKey
Value: "austin"
对象类型
Key: redisTemplateUserObjectKey
Value:
{
"@class": "com.example.jedisserializefrombytestojson.User",
"id": "123",
"name": "austin",
"age": 25
}
对象数组类型
Key: redisTemplateUserArrayObjectKey
Value:
[
"[Lcom.example.jedisserializefrombytestojson.User;",
[
{
"@class": "com.example.jedisserializefrombytestojson.User",
"id": "1",
"name": "austin1",
"age": 25
},
{
"@class": "com.example.jedisserializefrombytestojson.User",
"id": "2",
"name": "austin2",
"age": 25
}
]
]
JSONObject类型
Key: redisTemplateJSONObjectKey
Value:
{
"@class": "com.alibaba.fastjson.JSONObject",
"name": "austin",
"id": "123",
"age": 25
}
JSONArray类型
Key: redisTemplateJSONArrayKey
Value:
[
"com.alibaba.fastjson.JSONArray",
[
{
"@class": "com.alibaba.fastjson.JSONObject",
"name": "austin1",
"id": "1",
"age": 25
},
{
"@class": "com.alibaba.fastjson.JSONObject",
"name": "austin2",
"id": "1",
"age": 25
}
]
]
运行 redisTemplateSerializeTest 测试类,结果发现该方式序列化和反序列化都没有问题,果然是通用性序列化方式:
我们来思考下,在将一个对象序列化成一个字符串,怎么保证字符串反序列化成对象的类型呢?Jackson 通过 Default Typing,会在字符串多冗余一个类型,这样反序列化就知道具体的类型了。
从结果发现,使用 GenericJackson2JsonRedisSerializer 序列化方式,String 类型、对象、对象数组、JSONObject、JSONArray 序列化和反序列化都没有问题,value 值序列化后多了 @class 属性,反序列化的对象的类型就可以从这里获取到。@class 属性完美解决了反序列化后的对象类型,所以实际项目中,目前很多采用 GenericJackson2JsonRedisSerializer 序列化方式。
Jackson2JsonRedisSerializer
org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer
观察 redis 数据的存储格式:
key- value:
字符串类型
Key: redisTemplateStringKey
Value: "austin"
对象类型
Key: redisTemplateUserObjectKey
Value:
{
"id": "123",
"name": "austin",
"age": 25
}
与上面 GenericJackson2JsonRedisSerializer 序列化方式结果不同的是,value 没有 @class 属性。
对象数组类型
Key: redisTemplateUserArrayObjectKey
Value:
[
{
"id": "1",
"name": "austin1",
"age": 25
},
{
"id": "2",
"name": "austin2",
"age": 25
}
]
与上面 GenericJackson2JsonRedisSerializer 序列化方式结果不同的是,value 没有"@class": "com.example.jedisserializefrombytestojson.User"对象类型属性。
JSONObject类型
Key: redisTemplateJSONObjectKey
Value:
{
"name": "austin",
"id": "123",
"age": 25
}
与上面 GenericJackson2JsonRedisSerializer 序列化方式结果不同的是,value 没有"@class": "com.alibaba.fastjson.JSONObject"属性。
JSONArray类型
Key: redisTemplateJSONArrayKey
Value:
[
{
"name": "austin1",
"id": "1",
"age": 25
},
{
"name": "austin2",
"id": "1",
"age": 25
}
]
与上面 GenericJackson2JsonRedisSerializer 序列化方式结果不同的是,value 没有"com.alibaba.fastjson.JSONArray"对象类型属性。
Jackson2JsonRedisSerializer 与 GenericJackson2JsonRedisSerializer 序列化结果不同的是,前者并没有 @class 或者 @type 类型属性,这种序列化方式可能会导致获取 redis 数据反序列化成 POJO 对象时候出错,导致反序列化失败,所以一般也很少使用该方式。
比如在对象强制转换的情况,会报错:
报错信息很明显,不能直接将 JSONObject 对象强制转换成 User 对象,不能通过方式获取转换:
//该方式强转会报错
User userGet = (User) redisTemplate.opsForValue().get(redisTemplateUserObjectKey);
而正确的方式应该是:
//通过com.fastxml.jackson的ObjectMapper对象进行转换
Object userObject = redisTemplate.opsForValue().get(redisTemplateUserObjectKey);
ObjectMapper objectMapper = new ObjectMapper();
User userGet = objectMapper.convertValue(userObject, User.class);
System.out.println("userGet: " + userGet);
这也是 redis 序列化和反序列化主要非常注意地方。
总结
采用GenericJackson2JsonRedisSerializer序列化方式对于 String、对象、对象数组、JSONObject、JSONArray 的序列化反序列化操作都正常,对象强转是没有任何问题,但是采用Jackson2JsonRedisSerializer序列化方式在对象强制时,也就是使用 User userGet = (User) redisTemplate.opsForValue().get(redisTemplateUserObjectKey);方式获取对象,会操作对象转换失败,建议的解决方式是默认都采用 com.fastxml.jackson 的 ObjectMapper 对象进行转换,也就是:
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.convertValue(Object fromValue, Class<T> toValueType);
该方式支持将任意类型的 Object 对象转换为相应的实体对象。
