大家好,今天我们来聊聊 Spring 项目中 Redis 和 Jackson 的配置细节,尤其是 RedisConfig
和 CacheWrapper
这两个类的代码。这俩家伙在缓存系统中特别常见,理解它们能让你在项目中用 Redis 存数据时少踩坑。咱们会尽量用大白话,一步步拆开代码,讲得细到不能再细,还要接地气!
一、RedisConfig 类:打造 RedisTemplate 的"万能钥匙"
RedisConfig
是一个配置类,用来创建一个 RedisTemplate
对象。简单来说,RedisTemplate
是 Spring Data Redis 提供的一个工具,专门用来跟 Redis 数据库打交道,比如存数据、取数据啥的。咱们直接看代码:
java
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
// 使用String序列化器处理Key
template.setKeySerializer(new StringRedisSerializer());
// 使用JSON序列化器处理Value
Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper om = new ObjectMapper();
om.registerModule(new JavaTimeModule());
om.activateDefaultTyping(om.getPolymorphicTypeValidator(),
ObjectMapper.DefaultTyping.NON_FINAL);
serializer.setObjectMapper(om);
template.setValueSerializer(serializer);
template.setHashValueSerializer(serializer);
return template;
}
}
1. @Configuration
和 @Bean
:Spring 的"魔法标签"
@Configuration
:这个注解告诉 Spring:"嘿,这个类是用来配置东西的,里面有重要的初始化代码,你得认真对待!"。简单说,它标记了RedisConfig
是个配置类。@Bean
:这个注解用在方法上,表示这个方法会返回一个对象(这里是RedisTemplate
),Spring 会把它塞进自己的容器里管理。以后项目里哪需要用RedisTemplate
,都可以直接从 Spring 拿。
2. RedisConnectionFactory
:Redis 的"门票"
- 方法参数里有个
RedisConnectionFactory factory
,这是 Spring 注入进来的一个工厂类。你可以把它想象成一张"门票",有了它,RedisTemplate
才能连上 Redis 数据库。 - 配置好连接后,咱们通过
template.setConnectionFactory(factory)
把这张门票交给RedisTemplate
,让它知道去哪儿找 Redis。
3. 创建 RedisTemplate<String, Object>
:定义"主角"
RedisTemplate<String, Object> template = new RedisTemplate<>()
:这里我们创建了一个RedisTemplate
对象,泛型是<String, Object>
。String
是 Key(键)的类型,意思是 Redis 里的键都用字符串表示,比如"user:123"
。Object
是 Value(值)的类型,意思是值可以是任意对象,比如一个 User 对象、List 啥的,超级灵活。
4. Key 的序列化:StringRedisSerializer
template.setKeySerializer(new StringRedisSerializer())
:这行代码指定了 Key 的序列化器是StringRedisSerializer
。- 啥是序列化? 简单说,Redis 只认识字节流(byte[]),而我们代码里用的是 Java 对象(比如 String),序列化就是把 Java 对象转成字节流的过程,反过来叫反序列化。
- 为啥用
StringRedisSerializer
? 因为 Key 是字符串,用这个序列化器最直接,存进去是啥样,取出来还是啥样。比如存"user:123"
,Redis 里就是"user:123"
,不会有乱七八糟的编码问题。
5. Value 的序列化:Jackson2JsonRedisSerializer
-
Value 的处理就复杂点了,因为值是
Object
类型,可能是个对象、集合啥的,不能简单存成字符串。我们用的是Jackson2JsonRedisSerializer
,它会把对象转成 JSON 格式存进 Redis。 -
代码拆解:
javaJackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);
Jackson2JsonRedisSerializer
是基于 Jackson 库的序列化器,<Object>
表示它能处理任意类型的值。- 比如你存个
User
对象{name: "张三", age: 18}
,它会转成 JSON 字符串{"name": "张三", "age": 18}
存进 Redis。
配置 Jackson 的 ObjectMapper
-
ObjectMapper
是 Jackson 的核心工具,用来控制 JSON 的序列化和反序列化行为。咱们在这儿给它加了点料:javaObjectMapper om = new ObjectMapper(); om.registerModule(new JavaTimeModule()); om.activateDefaultTyping(om.getPolymorphicTypeValidator(), ObjectMapper.DefaultTyping.NON_FINAL); serializer.setObjectMapper(om);
-
JavaTimeModule
:- 这个模块是为了支持 Java 8 的时间和日期类型,比如
LocalDateTime
、Instant
这些。 - 如果不加这个模块,存个
LocalDateTime
对象可能会报错,或者格式乱掉。加了它,时间类型就能正常转成 JSON,比如"2023-10-25T10:00:00"
。
- 这个模块是为了支持 Java 8 的时间和日期类型,比如
-
activateDefaultTyping
:om.activateDefaultTyping(om.getPolymorphicTypeValidator(), ObjectMapper.DefaultTyping.NON_FINAL)
这行有点绕,咱们慢慢讲。- 啥意思? 默认情况下,Jackson 序列化对象时不会记录类型信息。反序列化时,它就不知道这个 JSON 应该还原成啥类型。
- 加了啥? 这里启用了类型信息,模式是
NON_FINAL
,意思是非final
类的对象都会带上类型标记。 - 举个栗子: 假设你存了个
User
对象{name: "张三"}
,没类型信息就是{"name": "张三"}
,加上类型后可能是["com.example.User", {"name": "张三"}]
。反序列化时,Jackson 看到类型标记就知道要转成User
类,不会出错。 - 为啥用
NON_FINAL
? 因为final
类的子类没法继承,类型固定,不需要额外标记。非final
类可能有子类,类型信息就很重要。
-
serializer.setObjectMapper(om)
:- 把配置好的
ObjectMapper
交给Jackson2JsonRedisSerializer
,让它用这个规则去序列化 Value。
- 把配置好的
6. 设置 Value 和 HashValue 的序列化器
template.setValueSerializer(serializer)
:普通的值(比如set("key", value)
的 value)用这个 Jackson 序列化器。template.setHashValueSerializer(serializer)
:Hash 类型的值(比如hset("key", "field", value)
的 value)也用这个序列化器。- 啥是 Hash? Redis 的 Hash 类似于 Java 的 Map,一个 Key 下可以存多个 field-value 对。用一样的序列化器,保证一致性。
7. 返回 RedisTemplate
return template
:配置完后,把这个RedisTemplate
返回,Spring 会把它放进容器,供项目其他地方用。
二、CacheWrapper 类:给缓存数据加个"保质期"
CacheWrapper
是一个包装类,用来把缓存数据和它的过期时间包在一起。代码如下:
java
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CacheWrapper<T> {
@JsonInclude(Include.NON_NULL)
private T data;
@JsonDeserialize(using = LongDeserializer.class)
@JsonSerialize(using = ToStringSerializer.class)
private Long expireTime;
public boolean isExpired() {
return System.currentTimeMillis() > expireTime;
}
}
1. Lombok 注解:省事的"魔法"
@Data
:自动生成 getter、setter、toString、equals、hashCode 方法,省得手写。@AllArgsConstructor
:生成全参构造器,比如new CacheWrapper(data, expireTime)
。@NoArgsConstructor
:生成无参构造器,比如new CacheWrapper()
,Jackson 反序列化时需要这个。
2. 泛型 <T>
:啥都能装
CacheWrapper<T>
用泛型T
,表示data
可以是任意类型,比如String
、User
、List<Integer>
,灵活得一批。
3. data
字段:缓存的核心数据
@JsonInclude(Include.NON_NULL)
:- 这个注解的意思是,如果
data
是null
,序列化成 JSON 时就不包含这个字段。 - 举个栗子: 假设
data = null, expireTime = 12345
,序列化后是{"expireTime": "12345"}
,而不是{"data": null, "expireTime": "12345"}
。这样 JSON 更干净,节省空间。
- 这个注解的意思是,如果
4. expireTime
字段:缓存的"保质期"
Long expireTime
:这是缓存的过期时间,用毫秒表示(比如System.currentTimeMillis() + 3600000
表示一小时后过期)。- 序列化问题:
Long
类型在 JSON 里可能会丢精度(尤其大数字时),所以用了自定义的序列化方式:@JsonSerialize(using = ToStringSerializer.class)
:- 序列化时,把
Long
转成字符串存进 JSON。比如expireTime = 123456789
,JSON 里是"123456789"
,而不是数字123456789
。 - 为啥?因为 JavaScript 处理超大数字时精度会丢,转成字符串就没这问题。
- 序列化时,把
@JsonDeserialize(using = LongDeserializer.class)
:- 反序列化时,把 JSON 里的字符串(比如
"123456789"
)转回Long
类型。 - 这俩注解配对使用,保证存取一致。
- 反序列化时,把 JSON 里的字符串(比如
5. isExpired()
方法:检查"保质期"
public boolean isExpired() { return System.currentTimeMillis() > expireTime; }
:- 这个方法很简单,拿当前时间跟
expireTime
比,如果当前时间超过了expireTime
,说明缓存过期了,返回true
。 - 栗子: 当前时间是
10000
,expireTime = 9000
,10000 > 9000
,返回true
,缓存过期。
- 这个方法很简单,拿当前时间跟
三、整体咋用?接地气的场景
-
存缓存:
javaRedisTemplate<String, Object> redisTemplate = // 从 Spring 拿 CacheWrapper<User> wrapper = new CacheWrapper<>(new User("张三", 18), System.currentTimeMillis() + 3600000); redisTemplate.opsForValue().set("user:123", wrapper);
- 存进 Redis 后,可能是这样的 JSON:
{"data": {"name": "张三", "age": 18}, "expireTime": "1698316800000"}
。
- 存进 Redis 后,可能是这样的 JSON:
-
取缓存:
javaCacheWrapper<User> wrapper = (CacheWrapper<User>) redisTemplate.opsForValue().get("user:123"); if (wrapper != null && !wrapper.isExpired()) { User user = wrapper.getData(); System.out.println(user.getName()); // 输出 "张三" } else { System.out.println("缓存过期或不存在"); }
四、总结:为啥这么配?
RedisTemplate
:- 用
StringRedisSerializer
让 Key 简单清晰。 - 用
Jackson2JsonRedisSerializer
让 Value 能存复杂对象,还支持 Java 8 时间类型和类型信息。
- 用
CacheWrapper
:- 把数据和过期时间包一起,方便判断缓存是否有效。
- 用 Jackson 注解优化 JSON 格式,处理
Long
精度问题。
这套配置接地气又实用,适合大部分缓存场景。希望这篇博客讲得够细、够明白,如果你还有啥疑问,欢迎留言,咱们接着聊!