我来详细介绍如何在 Spring Boot 项目中集成和使用 Redis。
在 pom.xml 中添加 Spring Data Redis 依赖:
pom.xml
org.springframework.boot spring-boot-starter-data-redis org.apache.commons commons-pool2 com.fasterxml.jackson.core jackson-databind
在 application.yml 或 application.properties 中配置 Redis:
application.yml
application.properties
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 # 连接池最大阻塞等待时间
创建配置类以自定义序列化方式:
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 redisTemplate(RedisConnectionFactory connectionFactory) { RedisTemplate template = new RedisTemplate<>(); template.setConnectionFactory(connectionFactory); // 使用 Jackson2JsonRedisSerializer 来序列化和反序列化 value Jackson2JsonRedisSerializer 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; } } 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 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 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 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 方法说明StringopsForValue()字符串操作HashopsForHash()哈希表操作ListopsForList()列表操作SetopsForSet()集合操作Sorted SetopsForZSet()有序集合操作7. 注意事项序列化问题:默认使用 JDK 序列化,建议使用 JSON 序列化(如上面配置类所示)缓存穿透:查询不存在的数据,建议缓存空值或使用布隆过滤器缓存雪崩:大量缓存同时失效,建议设置随机过期时间缓存击穿:热点数据过期,建议使用互斥锁或永不过期连接池配置:根据实际并发量调整连接池参数二、为什么要编写redis配置类呢1. 可以直接使用,但有问题引入依赖后可以直接 @Autowired 注入使用,Spring Boot 会自动配置一个默认的 RedisTemplate。 @Autowired private RedisTemplate redisTemplate; // 可以直接用 但是,默认的 RedisTemplate 有一个严重问题:序列化方式!2. 默认序列化的问题Spring Boot 默认的 RedisTemplate 使用 JDK 序列化(JdkSerializationRedisSerializer),会导致:问题演示: // 存入数据 redisTemplate.opsForValue().set("user:1", user); 在 Redis 中看到的数据是这样的: Key: "\xac\xed\x00\x05t\x00\x06user:1" Value: "\xac\xed\x00\x05sr\x00\x1ccom.example.entity.User..." 问题有哪些?不可读:存储的是二进制数据,无法直接在 Redis 客户端查看占用空间大:JDK 序列化后的数据比 JSON 大很多跨语言不兼容:其他语言(如 Python、Go)无法读取 Java 序列化的数据安全风险:JDK 序列化存在已知的安全漏洞对比:使用 JSON 序列化 Key: "user:1" Value: {"id":1,"name":"张三","age":25} 这样就:✅ 可读性强✅ 体积更小✅ 跨语言兼容✅ 更安全3. 为什么是 RedisTemplate<String, Object>?泛型说明 RedisTemplate // K: Key 的类型 // V: Value 的类型 为什么用 <String, Object>?Key 使用 String: // Redis 的 key 通常都是字符串 "user:1" "product:100" "cache:article:20" Value 使用 Object: // 可以存储各种类型的对象 redisTemplate.opsForValue().set("user:1", userObject); // User 对象 redisTemplate.opsForValue().set("count", 100); // Integer redisTemplate.opsForValue().set("list", Arrays.asList(1,2,3)); // List 使用 Object 类型最灵活,可以存储任何对象。4. 配置类做了什么?让我简化说明配置类的作用: @Bean public RedisTemplate redisTemplate(RedisConnectionFactory factory) { RedisTemplate template = new RedisTemplate<>(); template.setConnectionFactory(factory); // 关键:修改序列化方式 // 1. Key 使用 String 序列化 template.setKeySerializer(new StringRedisSerializer()); // 2. Value 使用 JSON 序列化 template.setValueSerializer(new Jackson2JsonRedisSerializer<>(Object.class)); // 3. Hash 的 Key 和 Value 也设置序列化方式 template.setHashKeySerializer(new StringRedisSerializer()); template.setHashValueSerializer(new Jackson2JsonRedisSerializer<>(Object.class)); return template; } 核心作用就是:把默认的 JDK 序列化改成 JSON 序列化!5. 不配置 vs 配置的对比场景:存储用户对象 User user = new User(1L, "张三", 25); redisTemplate.opsForValue().set("user:1", user); 不配置(使用默认)Redis 中存储的内容: 127.0.0.1:6379> keys * 1) "\xac\xed\x00\x05t\x00\x06user:1" 127.0.0.1:6379> get "\xac\xed\x00\x05t\x00\x06user:1" "\xac\xed\x00\x05sr\x00\x1c..." # 一堆乱码 配置后(使用 JSON)Redis 中存储的内容: 127.0.0.1:6379> keys * 1) "user:1" 127.0.0.1:6379> get user:1 "{\"id\":1,\"name\":\"张三\",\"age\":25}" 是不是清晰多了?总结可以不配置,但会用 JDK 序列化,导致数据不可读配置的目的:改用 JSON 序列化,让数据可读、节省空间、跨语言兼容<String, Object>:Key 用字符串,Value 用 Object 更灵活推荐做法:写配置类,统一使用 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 ops = redisTemplate.opsForValue(); ops.set("user:1", user); // 取出时也明确类型 User user = ops.get("user:1"); 总结这段代码的核心作用:setVisibility:让 Jackson 能访问 private 字段activateDefaultTyping:在 JSON 中存储类型信息(最重要!)目的:解决反序列化时类型丢失的问题不配置:取出来是 LinkedHashMap配置后:取出来是原始的 User 对象
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 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); } }
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 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 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); } }
@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); } }
opsForValue()
opsForHash()
opsForList()
opsForSet()
opsForZSet()
引入依赖后可以直接 @Autowired 注入使用,Spring Boot 会自动配置一个默认的 RedisTemplate。
@Autowired private RedisTemplate redisTemplate; // 可以直接用
但是,默认的 RedisTemplate 有一个严重问题:序列化方式!
Spring Boot 默认的 RedisTemplate 使用 JDK 序列化(JdkSerializationRedisSerializer),会导致:
// 存入数据 redisTemplate.opsForValue().set("user:1", user);
在 Redis 中看到的数据是这样的:
Key: "\xac\xed\x00\x05t\x00\x06user:1" Value: "\xac\xed\x00\x05sr\x00\x1ccom.example.entity.User..."
Key: "user:1" Value: {"id":1,"name":"张三","age":25}
这样就:
RedisTemplate // K: Key 的类型 // V: Value 的类型
<String, Object>
Key 使用 String:
// Redis 的 key 通常都是字符串 "user:1" "product:100" "cache:article:20"
Value 使用 Object:
// 可以存储各种类型的对象 redisTemplate.opsForValue().set("user:1", userObject); // User 对象 redisTemplate.opsForValue().set("count", 100); // Integer redisTemplate.opsForValue().set("list", Arrays.asList(1,2,3)); // List
使用 Object 类型最灵活,可以存储任何对象。
Object
让我简化说明配置类的作用:
@Bean public RedisTemplate redisTemplate(RedisConnectionFactory factory) { RedisTemplate template = new RedisTemplate<>(); template.setConnectionFactory(factory); // 关键:修改序列化方式 // 1. Key 使用 String 序列化 template.setKeySerializer(new StringRedisSerializer()); // 2. Value 使用 JSON 序列化 template.setValueSerializer(new Jackson2JsonRedisSerializer<>(Object.class)); // 3. Hash 的 Key 和 Value 也设置序列化方式 template.setHashKeySerializer(new StringRedisSerializer()); template.setHashValueSerializer(new Jackson2JsonRedisSerializer<>(Object.class)); return template; }
核心作用就是:把默认的 JDK 序列化改成 JSON 序列化!
User user = new User(1L, "张三", 25); redisTemplate.opsForValue().set("user:1", user);
Redis 中存储的内容:
127.0.0.1:6379> keys * 1) "\xac\xed\x00\x05t\x00\x06user:1" 127.0.0.1:6379> get "\xac\xed\x00\x05t\x00\x06user:1" "\xac\xed\x00\x05sr\x00\x1c..." # 一堆乱码
127.0.0.1:6379> keys * 1) "user:1" 127.0.0.1:6379> get user:1 "{\"id\":1,\"name\":\"张三\",\"age\":25}"
是不是清晰多了?
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 或其他通用类型。
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
ObjectMapper objectMapper = new ObjectMapper();
这是 Jackson 的核心类,负责 Java 对象和 JSON 之间的转换。
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
作用:告诉 Jackson 可以访问对象的所有属性
PropertyAccessor.ALL 包括:
FIELD
GETTER
SETTER
CREATOR
IS_GETTER
JsonAutoDetect.Visibility.ANY 表示:
public
protected
private
public class User { private Long id; // private 字段 private String name; // private 字段 // 没有 getter/setter 也能序列化! }
不设置这个配置的话:
// 默认只能访问 public 字段或有 getter/setter 的字段 // private 字段没有 getter 就无法序列化
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 } ]
Object obj = redisTemplate.opsForValue().get("user:1"); // obj 就是 User 类型!可以直接转换 User user = (User) obj; // ✅ 成功
LaissezFaireSubTypeValidator.instance
LaissezFaire
ObjectMapper.DefaultTyping.NON_FINAL
JAVA_LANG_OBJECT
OBJECT_AND_NON_CONCRETE
NON_CONCRETE_AND_ARRAYS
NON_FINAL
EVERYTHING
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);
// 使用 LaissezFaireSubTypeValidator 可能有安全风险 // 因为它允许反序列化任何类型 // 生产环境可以考虑更严格的验证器
类型信息会增加存储空间 // 原始:{"id":1,"name":"张三"} // 带类型:["com.example.User",{"id":1,"name":"张三"}]
如果不想使用类型信息,可以手动指定类型:
// 存入时就明确类型 ValueOperations ops = redisTemplate.opsForValue(); ops.set("user:1", user); // 取出时也明确类型 User user = ops.get("user:1");
这段代码的核心作用:
不配置:取出来是 LinkedHashMap配置后:取出来是原始的 User 对象
User