SpringBoot28-redis的配置
一、Spring Boot 中使用 Redis
我来详细介绍如何在 Spring Boot 项目中集成和使用 Redis。
1. 添加依赖
在 pom.xml 中添加 Spring Data Redis 依赖:
<dependencies>
<!-- Spring Boot Redis 依赖 -->
<!-- 为什么用这个?它自动配置 RedisTemplate 和 StringRedisTemplate -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- 连接池: Lettuce(Spring Boot 2.x 默认) -->
<!-- 为什么用 Lettuce 而不是 Jedis?
1. Lettuce 基于 Netty,支持异步和响应式编程
2. Lettuce 线程安全,单个连接可复用,Jedis 需要连接池
3. Spring Boot 2.x 官方默认支持 Lettuce -->
<dependency>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</dependency>
<!-- 如果需要使用 JSON 序列化 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<!-- 或者,JSON 处理 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.40</version>
</dependency>
</dependencies>
2. 配置文件
在 application.yml 或 application.properties 中配置 Redis:
application.yml:
spring:
redis:
host: localhost
port: 6379
password: # 如果有密码就填写
database: 0 # Redis 数据库索引(默认为0)
timeout: 3000ms # 连接超时时间
lettuce:
pool:
max-active: 8 # 连接池最大连接数
max-idle: 8 # 连接池最大空闲连接数
min-idle: 0 # 连接池最小空闲连接数
max-wait: -1ms # 连接池最大阻塞等待时间
3. Redis 配置类(推荐)
创建配置类以自定义序列化方式:
package com.example.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
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 {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
// 使用 Jackson2JsonRedisSerializer 来序列化和反序列化 value
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer =
new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.activateDefaultTyping(
LaissezFaireSubTypeValidator.instance,
ObjectMapper.DefaultTyping.NON_FINAL
);
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
// 使用 StringRedisSerializer 来序列化和反序列化 key
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
// key 采用 String 的序列化方式
template.setKeySerializer(stringRedisSerializer);
// hash 的 key 也采用 String 的序列化方式
template.setHashKeySerializer(stringRedisSerializer);
// value 序列化方式采用 jackson
template.setValueSerializer(jackson2JsonRedisSerializer);
// hash 的 value 序列化方式采用 jackson
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
}
或者,使用fastjson2序列化:
package com.example.demo.config;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONReader;
import com.alibaba.fastjson2.JSONWriter;
import com.alibaba.fastjson2.support.config.FastJsonConfig;
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.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
/**
* Redis 核心配置类
*
* 为什么需要这个配置?
* Spring Boot 自动配置的 RedisTemplate 使用 JDK 默认序列化(JdkSerializationRedisSerializer)
* 问题:
* 1. 序列化后的数据是二进制,Redis 命令行无法直接查看
* 2. 跨语言兼容性差(Java 特有的序列化格式)
* 3. 存储体积大
*
* 企业级方案:使用 JSON 序列化 + String 序列化 Key
*/
@Configuration
public class RedisConfig {
/**
* 配置 RedisTemplate<String, Object>
*
* 为什么 Key 用 String 序列化?
* - Redis Key 通常是字符串(如 "product:1001")
* - 使用 String 序列化后,Redis 命令行可以直接读取 Key
*
* 为什么 Value 用 JSON 序列化?
* - 对象存储为 JSON 字符串,人类可读
* - 跨语言兼容(Java、Python、Go 都能解析)
* - 存储体积小
*/
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
// Key 序列化:String
StringRedisSerializer stringSerializer = new StringRedisSerializer();
template.setKeySerializer(stringSerializer);
template.setHashKeySerializer(stringSerializer);
// Value 序列化:FastJSON2(比 Jackson 更快,企业级常用)
FastJson2JsonRedisSerializer<Object> jsonSerializer = new FastJson2JsonRedisSerializer<>(Object.class);
template.setValueSerializer(jsonSerializer);
template.setHashValueSerializer(jsonSerializer);
template.afterPropertiesSet();
return template;
}
/**
* 自定义 FastJSON2 序列化器
*
* 为什么不用 Spring 默认的 GenericJackson2JsonRedisSerializer?
* 1. FastJSON2 性能更好(阿里巴巴开源,经过双 11 验证)
* 2. 支持更多 Java 特性(如泛型)
*/
public static class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T> {
private final Class<T> clazz;
private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
public FastJson2JsonRedisSerializer(Class<T> clazz) {
this.clazz = clazz;
}
@Override
public byte[] serialize(T t) throws SerializationException {
if (t == null) {
return new byte[0];
}
// 使用 JSONWriter.Feature.WriteClassName 保留类型信息
// 这样反序列化时能正确还原对象类型
return JSON.toJSONString(t, JSONWriter.Feature.WriteClassName).getBytes(DEFAULT_CHARSET);
}
@Override
public T deserialize(byte[] bytes) throws SerializationException {
if (bytes == null || bytes.length == 0) {
return null;
}
String str = new String(bytes, DEFAULT_CHARSET);
// 使用 JSONReader.Feature.SupportAutoType 支持自动类型
return JSON.parseObject(str, clazz, JSONReader.Feature.SupportAutoType);
}
}
}
4. 使用方式
方式一:直接注入 RedisTemplate
package com.example.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
public class UserService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// 设置值
public void setUser(String key, User user) {
redisTemplate.opsForValue().set(key, user);
}
// 设置值并设置过期时间
public void setUserWithExpire(String key, User user, long timeout) {
redisTemplate.opsForValue().set(key, user, timeout, TimeUnit.SECONDS);
}
// 获取值
public User getUser(String key) {
return (User) redisTemplate.opsForValue().get(key);
}
// 删除
public void deleteUser(String key) {
redisTemplate.delete(key);
}
// 判断 key 是否存在
public boolean hasKey(String key) {
return Boolean.TRUE.equals(redisTemplate.hasKey(key));
}
// 设置过期时间
public void expire(String key, long timeout) {
redisTemplate.expire(key, timeout, TimeUnit.SECONDS);
}
}
方式二:封装 Redis 工具类(推荐)
package com.example.util;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.util.Collection;
import java.util.concurrent.TimeUnit;
@Component
public class RedisUtil {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// ========== String 操作 ==========
/**
* 设置缓存
*/
public void set(String key, Object value) {
redisTemplate.opsForValue().set(key, value);
}
/**
* 设置缓存并设置过期时间
*/
public void set(String key, Object value, long timeout, TimeUnit unit) {
redisTemplate.opsForValue().set(key, value, timeout, unit);
}
/**
* 获取缓存
*/
public Object get(String key) {
return redisTemplate.opsForValue().get(key);
}
/**
* 删除缓存
*/
public Boolean delete(String key) {
return redisTemplate.delete(key);
}
/**
* 批量删除
*/
public Long delete(Collection<String> keys) {
return redisTemplate.delete(keys);
}
/**
* 设置过期时间
*/
public Boolean expire(String key, long timeout, TimeUnit unit) {
return redisTemplate.expire(key, timeout, unit);
}
/**
* 判断 key 是否存在
*/
public Boolean hasKey(String key) {
return redisTemplate.hasKey(key);
}
/**
* 递增
*/
public Long increment(String key, long delta) {
return redisTemplate.opsForValue().increment(key, delta);
}
/**
* 递减
*/
public Long decrement(String key, long delta) {
return redisTemplate.opsForValue().decrement(key, delta);
}
// ========== Hash 操作 ==========
/**
* Hash 设置
*/
public void hSet(String key, String hashKey, Object value) {
redisTemplate.opsForHash().put(key, hashKey, value);
}
/**
* Hash 获取
*/
public Object hGet(String key, String hashKey) {
return redisTemplate.opsForHash().get(key, hashKey);
}
/**
* Hash 删除
*/
public Long hDelete(String key, Object... hashKeys) {
return redisTemplate.opsForHash().delete(key, hashKeys);
}
// ========== List 操作 ==========
/**
* List 右侧推入
*/
public Long lPush(String key, Object value) {
return redisTemplate.opsForList().rightPush(key, value);
}
/**
* List 左侧弹出
*/
public Object lPop(String key) {
return redisTemplate.opsForList().leftPop(key);
}
// ========== Set 操作 ==========
/**
* Set 添加
*/
public Long sAdd(String key, Object... values) {
return redisTemplate.opsForSet().add(key, values);
}
/**
* Set 移除
*/
public Long sRemove(String key, Object... values) {
return redisTemplate.opsForSet().remove(key, values);
}
// ========== Sorted Set 操作 ==========
/**
* ZSet 添加
*/
public Boolean zAdd(String key, Object value, double score) {
return redisTemplate.opsForZSet().add(key, value, score);
}
}
5. 实际使用示例
@RestController
@RequestMapping("/api/user")
public class UserController {
@Autowired
private RedisUtil redisUtil;
// 缓存用户信息
@GetMapping("/{id}")
public User getUser(@PathVariable Long id) {
String key = "user:" + id;
// 先从缓存中获取
User user = (User) redisUtil.get(key);
if (user == null) {
// 缓存未命中,从数据库查询
user = userService.getUserById(id);
if (user != null) {
// 存入缓存,过期时间 30 分钟
redisUtil.set(key, user, 30, TimeUnit.MINUTES);
}
}
return user;
}
// 更新用户时删除缓存
@PutMapping("/{id}")
public void updateUser(@PathVariable Long id, @RequestBody User user) {
userService.updateUser(user);
// 删除缓存
redisUtil.delete("user:" + id);
}
}
6. 常见操作总结
| 数据类型 | RedisTemplate 方法 | 说明 |
|---|---|---|
| String | opsForValue() | 字符串操作 |
| Hash | opsForHash() | 哈希表操作 |
| List | opsForList() | 列表操作 |
| Set | opsForSet() | 集合操作 |
| Sorted Set | opsForZSet() | 有序集合操作 |
7. 注意事项
- 序列化问题:默认使用 JDK 序列化,建议使用 JSON 序列化(如上面配置类所示)
- 缓存穿透:查询不存在的数据,建议缓存空值或使用布隆过滤器
- 缓存雪崩:大量缓存同时失效,建议设置随机过期时间
- 缓存击穿:热点数据过期,建议使用互斥锁或永不过期
- 连接池配置:根据实际并发量调整连接池参数
二、为什么要编写redis配置类呢
1. 可以直接使用,但有问题:序列化
引入依赖后可以直接 @Autowired 注入使用,Spring Boot 会自动配置一个默认的 RedisTemplate。
@Autowired
private RedisTemplate redisTemplate; // 可以直接用
但是,默认的 RedisTemplate 有一个严重问题:序列化方式!
2. 什么是序列化?为什么 Redis 需要它?
2.1 核心问题:Java 对象不能直接存进 Redis
Redis 是一个键值对数据库,它存储的是字节数组(byte[])。但我们在 Java 代码中操作的是对象(比如 Product、User)。
所以中间必须有一个转换过程:
Java 对象 (Product)
↓
序列化:转换成 byte[]
↓
通过网络发送到 Redis 服务器
↓
Redis 存储 byte[]
取出来时反过来:
Redis 中的 byte[]
↓
通过网络返回给 Java 程序
↓
反序列化:byte[] 转换成 Java 对象 (Product)
↓
你的代码使用 Product 对象
这个"对象 ↔ 字节数组"的转换,就叫做序列化和反序列化。
2.2 Spring Boot 默认用的 JDK 序列化有什么问题?
默认的 JdkSerializationRedisSerializer 使用的是 Java 原生的序列化机制(ObjectOutputStream)。
假设你存一个商品对象:
Product product = new Product();
product.setId(1L);
product.setName("iPhone");
product.setPrice(5999.00);
redisTemplate.opsForValue().set("product:1", product);
存进 Redis 后,数据长这样:
\xac\xed\x00\x05sr\x00!com.example.demo.entity.Product\x00\x00\x00\x00\x00\x00\x00\x01\x02\x00\x03L\x00\x04namet\x00\x12Ljava/lang/String;L\x00\x05price...
这就是 JDK 序列化的结果——二进制乱码。
2.3 这带来了什么弊端?
| 问题 | 说明 |
|---|---|
| 人类不可读 | 打开 Redis 命令行,看到的是一堆乱码,无法调试 |
| 跨语言不兼容 | 只有 Java 能解析这种格式,Python/Go/Node.js 读不了 |
| 存储体积大 | 二进制格式包含类路径、字段类型等元信息,比 JSON 大很多 |
| 安全风险 | Java 原生序列化有反序列化漏洞(历史上出过多次安全事件) |
所以,企业级项目几乎不会用默认的 JDK 序列化。
3. 那应该用什么序列化方式?
企业级的标准方案:
| 数据类型 | 序列化方式 | 原因 |
|---|---|---|
| Key | String(UTF-8 字符串) | Key 本来就是字符串,如 "product:1001",人类可读 |
| Value | JSON 字符串 | 人类可读、跨语言、体积小、安全 |
目标效果:
// 存入时
redisTemplate.opsForValue().set("product:1", product);
// Redis 中实际存储的是:
// Key: product:1 (字符串,UTF-8 编码)
// Value: {"id":1,"name":"iPhone","price":5999.00} (JSON 字符串,UTF-8 编码)
4. 现在回头看 RedisConfig,逐行拆解
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
4.1 为什么方法参数是 RedisConnectionFactory?
没有它的问题: RedisTemplate 需要知道怎么连接 Redis 服务器(IP、端口、密码等)。
Spring Boot 已经自动配置好了 RedisConnectionFactory(根据你的 application.yml 中的 spring.redis.* 配置),所以这里直接注入进来用。
4.2 为什么返回类型是 RedisTemplate<String, Object> 而不是 RedisTemplate<Object, Object>?
RedisTemplate 有两个泛型参数: RedisTemplate<K, V>
-
K= Key 的类型 -
V= Value 的类型
默认自动配置的是 RedisTemplate<Object, Object>,Key 和 Value 都是 Object。
但企业级项目中:
-
Key 一定是字符串(如
"product:1"),所以第一个泛型固定为String -
Value 可能是任意对象(
Product、User、List等),所以第二个泛型是Object
这样设计的好处: 使用时类型安全,不需要强转。
// 用 RedisTemplate<String, Object>,Key 只能是 String
redisTemplate.opsForValue().set("product:1", product); // ✓ 正确
redisTemplate.opsForValue().set(123, product); // ✗ 编译报错,类型安全
4.3 第一行配置:Key 的序列化
StringRedisSerializer stringSerializer = new StringRedisSerializer();
template.setKeySerializer(stringSerializer);
template.setHashKeySerializer(stringSerializer);
为什么需要 setKeySerializer?
RedisTemplate 默认使用 JDK 序列化来处理 Key。这意味着:
// 默认情况下,你写的 Key 是 "product:1"
redisTemplate.opsForValue().set("product:1", product);
// 但实际存进 Redis 的 Key 是:
// \xac\xed\x00\x05t\x00\x09product:1 (JDK 序列化后的二进制)
问题: 你在 Redis 命令行里查不到这个 Key,因为它被序列化成二进制了。
StringRedisSerializer 的作用: 把字符串按 UTF-8 编码成 byte[],不做任何额外处理。
// 使用 StringRedisSerializer 后:
// Key: product:1 → UTF-8 编码 → [112, 114, 111, 100, 117, 99, 116, 58, 49]
// 存进 Redis 的就是纯文本 "product:1"
setHashKeySerializer 是什么?
Redis 有 Hash 数据结构(HSET key field value),这里的 field 也需要序列化。比如:
redisTemplate.opsForHash().put("user:1", "name", "张三");
// "user:1" 是 Key,用 setKeySerializer
// "name" 是 HashKey,用 setHashKeySerializer
| 参数 | 含义 | 对应层级 |
|---|---|---|
"user:1" | Redis 的 外层 key | 数据库中的键 |
"name" | Hash 结构里的 field(字段) | 内层 key |
"张三" | 该字段的 value | 内层 value |
普通 String:
key: "name" → value: "张三"
Hash:
key: "user:1" → value: { "name": "张三", "age": 25, "city": "北京" }
4.4 第二行配置:Value 的序列化
FastJson2JsonRedisSerializer<Object> jsonSerializer =
new FastJson2JsonRedisSerializer<>(Object.class);
template.setValueSerializer(jsonSerializer);
template.setHashValueSerializer(jsonSerializer);
为什么 Value 要用 JSON 序列化?
因为 Value 是对象(Product、List、Map),需要转换成可读的字符串格式。
FastJson2JsonRedisSerializer 是我自定义的类,它实现了 RedisSerializer 接口。Spring 要求序列化器必须实现这个接口:
public interface RedisSerializer<T> {
byte[] serialize(T t) throws SerializationException; // 对象 → byte[]
T deserialize(byte[] bytes) throws SerializationException; // byte[] → 对象
}
5. 重点:FastJson2JsonRedisSerializer 的每一行代码
public static class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T> {
5.1 为什么需要泛型 <T>?
因为序列化器是通用的,可以处理 Product、User、Order 等任何类型。用泛型避免每个类型都写一个序列化器。
private final Class<T> clazz;
为什么需要 Class<T>?
反序列化时,JSON 字符串 "{"id":1,"name":"iPhone"}" 需要知道要转换成哪个 Java 类。Class<T> 就是告诉它:"请把这个 JSON 转成 Product 对象"。
public FastJson2JsonRedisSerializer(Class<T> clazz) {
this.clazz = clazz;
}
5.2 serialize 方法:Java 对象 → byte[]
@Override
public byte[] serialize(T t) throws SerializationException {
if (t == null) {
return new byte[0]; // null 存为空字节数组
}
// JSON.toJSONString:把 Java 对象转成 JSON 字符串
// WriteClassName:在 JSON 中写入类名信息,方便反序列化时知道类型
String jsonString = JSON.toJSONString(t, JSONWriter.Feature.WriteClassName);
// getBytes:把字符串按 UTF-8 编码成 byte[]
return jsonString.getBytes(DEFAULT_CHARSET);
}
举个例子:
Product product = new Product();
product.setId(1L);
product.setName("iPhone");
// serialize(product) 的执行过程:
// 1. JSON.toJSONString(product) → "{\"@type\":\"com.example.demo.entity.Product\",\"id\":1,\"name\":\"iPhone\"}"
// 2. .getBytes(UTF_8) → [123, 34, 64, 116, 121, 112, 101, ...]
为什么要加 WriteClassName?
因为反序列化时需要知道 JSON 对应哪个 Java 类。如果不写类名:
{"id":1,"name":"iPhone"}
Redis 里只有这个 JSON,取出来时不知道要转成 Product 还是 Goods 还是别的什么。
加了 WriteClassName 后:
{"@type":"com.example.demo.entity.Product","id":1,"name":"iPhone"}
反序列化时看到 @type,就知道该创建 Product 对象。
5.3 deserialize 方法:byte[] → Java 对象
@Override
public T deserialize(byte[] bytes) throws SerializationException {
if (bytes == null || bytes.length == 0) {
return null;
}
// 1. byte[] → String(UTF-8 解码)
String str = new String(bytes, DEFAULT_CHARSET);
// 2. JSON 字符串 → Java 对象
// SupportAutoType:支持根据 @type 自动确定类型
return JSON.parseObject(str, clazz, JSONReader.Feature.SupportAutoType);
}
执行过程:
// Redis 返回的 byte[]:[123, 34, 64, 116, 121, 112, 101, ...]
// 1. new String(bytes, UTF_8) → "{\"@type\":\"com.example.demo.entity.Product\",...}"
// 2. JSON.parseObject(str, Product.class, SupportAutoType) → Product 对象
6. 完整的数据流转图
你的代码:
redisTemplate.opsForValue().set("product:1", product);
内部流程:
1. Key "product:1"
→ StringRedisSerializer.serialize()
→ "product:1".getBytes(UTF_8)
→ [112, 114, 111, 100, 117, 99, 116, 58, 49]
→ 发送到 Redis
2. Value product 对象
→ FastJson2JsonRedisSerializer.serialize()
→ JSON.toJSONString(product, WriteClassName)
→ "{\"@type\":\"...Product\",\"id\":1,\"name\":\"iPhone\"}"
→ .getBytes(UTF_8)
→ 发送到 Redis
Redis 存储:
Key: "product:1" (纯文本,人类可读)
Value: "{\"@type\":\"...Product\",\"id\":1,\"name\":\"iPhone\"}" (JSON 文本,人类可读)
取出时:
1. Redis 返回 Key 的 byte[]
→ StringRedisSerializer.deserialize()
→ new String(bytes, UTF_8)
→ "product:1"
2. Redis 返回 Value 的 byte[]
→ FastJson2JsonRedisSerializer.deserialize()
→ new String(bytes, UTF_8)
→ JSON.parseObject(json, Product.class, SupportAutoType)
→ Product 对象
7. 总结:RedisConfig 解决了什么问题
| 如果不写 RedisConfig | 写了 RedisConfig |
|---|---|
| Key 被 JDK 序列化成二进制乱码 | Key 是纯文本字符串,可读可调试 |
| Value 被 JDK 序列化成二进制 | Value 是 JSON 字符串,跨语言兼容 |
| Redis 命令行无法查看数据 | Redis 命令行可以直接看到 JSON |
| 只有 Java 能读取 | Python/Go/Node.js 都能解析 |
| 存储体积大 | JSON 体积小 |
| 有安全风险 | 更安全 |
一句话:RedisConfig 的作用就是替换 Spring Boot 默认的序列化器,让 Redis 中的数据变成人类可读的 JSON 格式。
三、配置类中的ObjectMapper的相关操作
redis配置类中有一段代码:
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.activateDefaultTyping( LaissezFaireSubTypeValidator.instance,
ObjectMapper.DefaultTyping.NON_FINAL );
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
让我逐行解释这段代码的作用。
这段代码在解决什么问题?
核心问题:反序列化时的类型丢失
当你从 Redis 取出数据时,Jackson 不知道原始对象是什么类型,只能反序列化成 LinkedHashMap 或其他通用类型。
问题演示
// 存入 User 对象
User user = new User(1L, "张三", 25);
redisTemplate.opsForValue().set("user:1", user);
// 取出时
Object obj = redisTemplate.opsForValue().get("user:1");
System.out.println(obj.getClass());
// 输出:class java.util.LinkedHashMap(不是 User!)
// 无法直接使用
User user = (User) obj; // 报错:ClassCastException
逐行解释
1. 创建 ObjectMapper
ObjectMapper objectMapper = new ObjectMapper();
这是 Jackson 的核心类,负责 Java 对象和 JSON 之间的转换。
2. 设置可见性
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
作用:告诉 Jackson 可以访问对象的所有属性
PropertyAccessor.ALL 包括:
FIELD(字段)GETTER(get 方法)SETTER(set 方法)CREATOR(构造方法)IS_GETTER(is 方法)
JsonAutoDetect.Visibility.ANY 表示:
public可以访问protected可以访问private也可以访问 ⬅️ 关键
示例:
public class User {
private Long id; // private 字段
private String name; // private 字段
// 没有 getter/setter 也能序列化!
}
不设置这个配置的话:
// 默认只能访问 public 字段或有 getter/setter 的字段
// private 字段没有 getter 就无法序列化
3. 激活默认类型信息(重点!)
objectMapper.activateDefaultTyping(
LaissezFaireSubTypeValidator.instance,
ObjectMapper.DefaultTyping.NON_FINAL
);
这是最关键的配置!作用:在 JSON 中存储类型信息
不配置时的问题:
存入 Redis:
{
"id": 1,
"name": "张三",
"age": 25
}
从 Redis 取出:
Object obj = redisTemplate.opsForValue().get("user:1");
// obj 是 LinkedHashMap,不是 User!
// 因为 Jackson 不知道原始类型是什么
配置后的效果:
存入 Redis(包含类型信息):
[
"com.example.entity.User",
{
"id": 1,
"name": "张三",
"age": 25
}
]
从 Redis 取出:
Object obj = redisTemplate.opsForValue().get("user:1");
// obj 就是 User 类型!可以直接转换
User user = (User) obj; // ✅ 成功
参数说明:
LaissezFaireSubTypeValidator.instance
- 一个宽松的类型验证器
- 允许反序列化几乎所有类型
LaissezFaire是法语,意思是"放任自由"
ObjectMapper.DefaultTyping.NON_FINAL
- 为非 final 类添加类型信息
- 选项包括:
JAVA_LANG_OBJECT:只对 Object 类型OBJECT_AND_NON_CONCRETE:Object 和抽象类/接口NON_CONCRETE_AND_ARRAYS:抽象类、接口和数组NON_FINAL:所有非 final 类 ⬅️ 最常用EVERYTHING:所有类型
4. 设置到序列化器
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
把配置好的 ObjectMapper 设置给 Jackson 序列化器,让它使用我们的配置。
完整效果对比
配置前(类型丢失)
// 存入
User user = new User(1L, "张三", 25);
redisTemplate.opsForValue().set("user:1", user);
// Redis 中存储:
{
"id": 1,
"name": "张三",
"age": 25
}
// 取出
Object obj = redisTemplate.opsForValue().get("user:1");
System.out.println(obj.getClass());
// 输出:class java.util.LinkedHashMap ❌
User user = (User) obj; // 报错!ClassCastException
配置后(保留类型)
// 存入
User user = new User(1L, "张三", 25);
redisTemplate.opsForValue().set("user:1", user);
// Redis 中存储:
[
"com.example.entity.User",
{
"id": 1,
"name": "张三",
"age": 25
}
]
// 取出
Object obj = redisTemplate.opsForValue().get("user:1");
System.out.println(obj.getClass());
// 输出:class com.example.entity.User ✅
User user = (User) obj; // 成功!
代码简化版(帮助理解)
ObjectMapper objectMapper = new ObjectMapper();
// 1. 让 Jackson 能访问 private 字段
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
// 2. 在 JSON 中保存类型信息(最重要!)
objectMapper.activateDefaultTyping(
LaissezFaireSubTypeValidator.instance, // 验证器:允许所有类型
ObjectMapper.DefaultTyping.NON_FINAL // 为非 final 类添加类型信息
);
// 3. 应用这些配置
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
注意事项
1. 安全风险
// 使用 LaissezFaireSubTypeValidator 可能有安全风险
// 因为它允许反序列化任何类型
// 生产环境可以考虑更严格的验证器
2. 性能影响
类型信息会增加存储空间
// 原始:{"id":1,"name":"张三"}
// 带类型:["com.example.User",{"id":1,"name":"张三"}]
3. 替代方案
如果不想使用类型信息,可以手动指定类型:
// 存入时就明确类型
ValueOperations<String, User> ops = redisTemplate.opsForValue();
ops.set("user:1", user);
// 取出时也明确类型
User user = ops.get("user:1");
总结
这段代码的核心作用:
- setVisibility:让 Jackson 能访问 private 字段
- activateDefaultTyping:在 JSON 中存储类型信息(最重要!)
- 目的:解决反序列化时类型丢失的问题
不配置:取出来是 LinkedHashMap
配置后:取出来是原始的 User 对象
更多推荐


所有评论(0)