0%

springboot集成redis乱码问题

Spring Boot 集成 Redis 乱码问题详解:原因分析与解决方案

在 Spring Boot 集成 Redis 时,若使用默认配置的 RedisTemplate 操作字符串,常出现 key/value 乱码(如前缀含 \xac\xed\x00\x05t\x00\x03 等特殊字符)。核心原因是 RedisTemplate 默认采用 JdkSerializationRedisSerializer 序列化方式(基于 JDK 原生序列化,会生成二进制数据),而 StringRedisTemplate 或自定义序列化配置可彻底解决该问题。从 “乱码原因→两种解决方案→序列化对比→实战示例” 四个维度,系统讲解 Redis 乱码的解决方法。

乱码的根本原因:默认序列化方式不匹配

首先需明确:Redis 本身是 二进制安全的键值存储,不限制数据格式,但 Spring Boot 提供的 RedisTemplate 默认序列化方式会导致 “人类不可读” 的二进制数据,被误认为 “乱码”。

1. RedisTemplate 默认序列化配置

RedisTemplate 对不同类型的序列化器默认配置如下:

数据类型 默认序列化器 序列化结果特点
Key(键) JdkSerializationRedisSerializer 生成二进制数据,前缀含特殊字符(如 \xac\xed),乱码
Value(值) JdkSerializationRedisSerializer 同上,二进制数据,不可读
Hash Key JdkSerializationRedisSerializer 同上
Hash Value JdkSerializationRedisSerializer 同上
问题根源:JdkSerializationRedisSerializer 的特性
  • 原理:基于 JDK ObjectOutputStream 实现序列化,要求被序列化的对象实现 Serializable 接口;
  • 结果:序列化后的数据是 二进制字节数组,存储到 Redis 后显示为乱码(如 \xac\xed\x00\x05t\x00\x03key);
  • 场景不匹配:若仅操作字符串(如 String 类型的 key/value),二进制序列化完全没必要,且导致乱码。

2. 示例:默认配置的乱码效果

使用默认 RedisTemplate 存储字符串:

1
2
3
4
5
6
7
@Autowired
private RedisTemplate<String, String> redisTemplate;

@Test
public void testDefaultSerialization() {
redisTemplate.opsForValue().set("name", "张三");
}

在 Redis 客户端(如 redis-cli)查看结果:

1
2
127.0.0.1:6379> get "\xac\xed\x00\x05t\x00\x04name"  # key 乱码
"\xac\xed\x00\x05t\x00\x06\xe5\xbc\xa0\xe4\xb8\x89" # value 乱码

可见 key 和 value 均含 \xac\xed 等特殊字符,完全不可读 —— 这就是典型的默认序列化导致的乱码问题。

解决方案一:直接使用 StringRedisTemplate(推荐字符串场景)

StringRedisTemplateRedisTemplate 的子类,专门为 String 类型 设计,默认采用 StringRedisSerializer(字符串序列化,UTF-8 编码),完美解决乱码问题,且无需额外配置。

1. StringRedisTemplate 的核心特性

数据类型 序列化器 序列化结果特点
Key(键) StringRedisSerializer 纯字符串,UTF-8 编码,无乱码
Value(值) StringRedisSerializer 同上
Hash Key StringRedisSerializer 同上
Hash Value StringRedisSerializer 同上
优势:
  • 零配置:直接注入即可使用,无需自定义 Bean;
  • 无乱码:序列化结果为人类可读的字符串,与 redis-cli 操作结果一致;
  • 轻量级:仅处理 String 类型,性能优于 RedisTemplate 的通用序列化。

2. 实战示例:使用 StringRedisTemplate

(1)注入 StringRedisTemplate
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class RedisStringController {

// 注入 StringRedisTemplate(无需额外配置)
@Autowired
private StringRedisTemplate stringRedisTemplate;

// 存储 String 类型数据
@GetMapping("/redis/set/{key}/{value}")
public String setString(@PathVariable String key, @PathVariable String value) {
// 操作字符串:opsForValue()
stringRedisTemplate.opsForValue().set(key, value);
return "存储成功:key=" + key + ", value=" + value;
}

// 获取 String 类型数据
@GetMapping("/redis/get/{key}")
public String getString(@PathVariable String key) {
String value = stringRedisTemplate.opsForValue().get(key);
return "获取结果:key=" + key + ", value=" + value;
}
}
(2)验证结果
  1. 调用接口存储数据:http://localhost:8080/redis/set/name/张三

  2. 在redis-cli查看:

    1
    2
    127.0.0.1:6379> get name  # key 无乱码
    "张三" # value 无乱码
适用场景:
  • 仅操作 String 类型的 key/value(如缓存用户 Token、验证码、简单字符串数据);
  • 需与 redis-cli 或其他语言客户端(如 Python/Go 的 Redis 客户端)交互,要求数据可读。

解决方案二:自定义 RedisTemplate 序列化(支持复杂对象)

若需存储 复杂对象(如 UserOrder 实体类),StringRedisTemplate 无法直接使用(需手动转 JSON),此时需自定义 RedisTemplate 的序列化器 —— 将 Key 用 StringRedisSerializer(避免乱码),Value 用 Jackson2JsonRedisSerializer(JSON 序列化,兼顾可读性和对象转换)。

1. 核心序列化器选择

(1)Key 序列化:StringRedisSerializer
  • 原因:Key 通常是简单字符串(如 user:1001),用字符串序列化可确保无乱码,且与其他客户端兼容;
  • 编码:UTF-8,支持中文 key(如 用户:1001)。
(2)Value 序列化:Jackson2JsonRedisSerializer
  • 原因:JSON 序列化的优势:
    1. 可读性强:序列化结果为 JSON 字符串,redis-cli 可直接查看;
    2. 无需实现 Serializable:避免 JDK 序列化的接口强制要求;
    3. 跨语言兼容:JSON 是通用格式,其他语言客户端可解析;
  • 依赖:需引入 Jackson 依赖(Spring Boot Web 依赖已包含,无需额外添加)。

2. 自定义 RedisTemplate 配置(完整代码)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
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.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
public class RedisConfig {

/**
* 自定义 RedisTemplate,解决乱码问题,支持复杂对象存储
*/
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
// 1. 创建 RedisTemplate 实例
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
// 2. 设置 Redis 连接工厂
redisTemplate.setConnectionFactory(redisConnectionFactory);

// 3. 配置 Jackson2JsonRedisSerializer(Value 序列化)
Jackson2JsonRedisSerializer<Object> jacksonSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper objectMapper = new ObjectMapper();

// 3.1 配置 Jackson 序列化规则:支持所有字段(包括 private)
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
// 3.2 解决 JDK 8 日期时间(LocalDateTime)序列化问题
objectMapper.registerModule(new JavaTimeModule());
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
// 3.3 注入 Jackson 配置到序列化器
jacksonSerializer.setObjectMapper(objectMapper);

// 4. 配置 StringRedisSerializer(Key/Hash Key 序列化)
StringRedisSerializer stringSerializer = new StringRedisSerializer();

// 5. 为 RedisTemplate 设置序列化器
redisTemplate.setKeySerializer(stringSerializer); // Key 序列化
redisTemplate.setValueSerializer(jacksonSerializer); // Value 序列化
redisTemplate.setHashKeySerializer(stringSerializer); // Hash Key 序列化
redisTemplate.setHashValueSerializer(jacksonSerializer); // Hash Value 序列化

// 6. 初始化 RedisTemplate(必须调用,否则配置不生效)
redisTemplate.afterPropertiesSet();

return redisTemplate;
}
}
关键优化点:
  • 日期序列化:通过 JavaTimeModule 支持 JDK 8 的 LocalDateTime/LocalDate,避免默认时间戳格式(可读性差);
  • Hash 序列化:同时配置 Hash Key 和 Hash Value 的序列化,解决 Hash 类型数据的乱码;
  • afterPropertiesSet():初始化方法,确保序列化器配置生效(不可省略)。

3. 实战示例:存储复杂对象

(1)定义实体类(无需实现 Serializable)
1
2
3
4
5
6
7
8
9
10
11
import java.time.LocalDateTime;

// 无需实现 Serializable 接口
public class User {
private Long id;
private String name;
private Integer age;
private LocalDateTime createTime; // JDK 8 日期类型

// 省略 getter/setter/toString 方法
}
(2)使用自定义 RedisTemplate 操作对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import java.time.LocalDateTime;

@RestController
public class RedisObjectController {

// 注入自定义的 RedisTemplate(泛型为 <String, Object>)
@Autowired
private RedisTemplate<String, Object> redisTemplate;

// 存储 User 对象
@GetMapping("/redis/set/user/{id}")
public String setUser(@PathVariable Long id) {
// 构造 User 对象
User user = new User();
user.setId(id);
user.setName("张三");
user.setAge(25);
user.setCreateTime(LocalDateTime.now());

// 存储到 Redis:key = "user:{id}",value = User 对象
redisTemplate.opsForValue().set("user:" + id, user);
return "存储 User 成功:" + user.toString();
}

// 获取 User 对象
@GetMapping("/redis/get/user/{id}")
public User getUser(@PathVariable Long id) {
// 从 Redis 获取,自动反序列化为 User 对象
String key = "user:" + id;
User user = (User) redisTemplate.opsForValue().get(key);
return user;
}
}
(3)验证结果
  1. 调用接口存储对象:http://localhost:8080/redis/set/user/1001

  2. 在redis-cli查看:

    1
    2
    127.0.0.1:6379> get "user:1001"  # key 无乱码
    "{\"id\":1001,\"name\":\"张三\",\"age\":25,\"createTime\":\"2024-05-20T16:30:00\"}" # Value 为 JSON 字符串,无乱码
适用场景:
  • 需存储复杂对象(如实体类、集合);
  • 需兼顾数据可读性和对象自动转换(无需手动 JSON 序列化 / 反序列化)。

三种序列化方式对比(避免选择误区)

为了更清晰选择序列化方案,对比常见的三种 Redis 序列化器:

序列化器 核心特点 优点 缺点 适用场景
JdkSerializationRedisSerializer(默认) JDK 原生序列化,二进制数据 支持任何实现 Serializable 的对象 乱码、不可读、需实现接口、跨语言差 仅存储 Java 内部对象,不与其他客户端交互
StringRedisSerializer 字符串序列化,UTF-8 编码 无乱码、可读性强、跨语言兼容、轻量 仅支持 String 类型,不支持复杂对象 存储 String 类型的 key/value(如 Token、验证码)
Jackson2JsonRedisSerializer JSON 序列化,字符串格式 无乱码、可读性强、支持复杂对象、跨语言兼容 需配置 Jackson、性能略低于 JDK 序列化 存储复杂对象(如实体类),需跨语言交互

结论

  • 字符串场景:优先用 StringRedisTemplateStringRedisSerializer);
  • 复杂对象场景:用自定义 RedisTemplate(Key 用 StringRedisSerializer,Value 用 Jackson2JsonRedisSerializer);
  • 避免用默认 RedisTemplateJdkSerializationRedisSerializer),除非是纯 Java 内部的二进制对象存储。

常见问题与解决方案

1. 自定义 RedisTemplate 后仍乱码

问题原因:
  • 未配置 Hash 序列化(仅配置了 Key/Value,Hash Key/Hash Value 仍用默认 JDK 序列化);
  • 忘记调用 redisTemplate.afterPropertiesSet(),配置未生效;
  • 注入的是默认 RedisTemplate,而非自定义的 RedisTemplate(如泛型不匹配)。
解决方案:
  • 完整配置 Key/Hash Key/Value/Hash Value 四个序列化器;
  • 确保调用 afterPropertiesSet()
  • 注入时确认泛型:@Autowired private RedisTemplate<String, Object> redisTemplate(与自定义 Bean 一致)。

2. LocalDateTime 序列化失败(报 “no serializer found”)

问题原因:
  • Jackson 默认不支持 JDK 8 新日期类型(LocalDateTime/LocalDate),需手动注册 JavaTimeModule
解决方案:

ObjectMapper 中添加日期模块配置(已包含在自定义 RedisTemplate 代码中):

1
2
objectMapper.registerModule(new JavaTimeModule()); // 支持 JDK 8 日期
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); // 禁用时间戳格式,用 ISO 格式(如 "2024-05-20T16:30:00")

3. 与 StringRedisTemplate 混用导致的问题

问题描述:

RedisTemplate 存储 key="name", value="张三"(JSON 序列化),再用 StringRedisTemplate 获取 key="name",得到 "{\"name\":\"张三\"}"(带引号的 JSON 字符串),而非纯字符串。

原因:
  • RedisTemplate 存储 Value 为 JSON 字符串(如 "张三" 被序列化为 "\"张三\"");
  • StringRedisTemplate 读取时按纯字符串解析,保留了 JSON 的引号。
解决方案:
  • 同一 key 避免混用两种 Template;
  • 若需混用,读取时需手动处理(如用 Jackson 反序列化 StringRedisTemplate 获取的结果)。

总结

Spring Boot 集成 Redis 乱码的核心是 “序列化方式不匹配”,解决方案分两类:

  1. 字符串场景:直接使用 StringRedisTemplate,零配置,无乱码,推荐优先选择;
  2. 复杂对象场景:自定义 RedisTemplate,Key 用 StringRedisSerializer(无乱码),Value 用 Jackson2JsonRedisSerializer(JSON 序列化,支持对象)

欢迎关注我的其它发布渠道