redis 序列化 支持 PageImpl
既然 Redis 序列化器配置了activateDefaultTyping,且不能修改,那就只能让PageImpl具备被 Jackson 反序列化的能力。
一、核心思路
text
Jackson 反序列化 PageImpl 失败的原因: ↓ PageImpl 没有无参构造器 ↓ 解决方案:为 PageImpl 添加反序列化支持(不修改 Redis 配置) ↓ 方式1:Jackson Mixin(推荐) 方式2:自定义反序列化器并注册到 Redis 的 ObjectMapper 方式3:在 Redis 配置中注入自定义 ObjectMapper
二、方案一:Jackson Mixin(推荐,最简洁)
通过 Mixin 为PageImpl提供@JsonCreator注解,告诉 Jackson 如何构造PageImpl对象。
1. 创建 Mixin 类
java
import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import java.util.List; /** * PageImpl 的 Jackson Mixin,用于提供反序列化支持 */ public abstract class PageImplMixin<T> { @JsonCreator public PageImplMixin( @JsonProperty("content") List<T> content, @JsonProperty("page") int page, @JsonProperty("size") int size, @JsonProperty("total") long total) { // Mixin 方法不需要实现,只用于注解 } // 如果 pageable 是完整对象,也可以支持 @JsonCreator public PageImplMixin( @JsonProperty("content") List<T> content, @JsonProperty("pageable") Pageable pageable, @JsonProperty("total") long total) { // 另一个构造方法签名 } }2. 在 Redis 配置中注册 Mixin
找到 Redis 序列化器配置的地方,添加 Mixin:
java
@Configuration public class RedisConfig { @Bean public RedisCacheManager cacheManager(RedisConnectionFactory factory) { // 1. 创建 ObjectMapper ObjectMapper mapper = new ObjectMapper(); // 2. 注册 PageImpl 的 Mixin mapper.addMixIn(PageImpl.class, PageImplMixin.class); // 3. 注册 JavaTimeModule(支持 LocalDate/LocalDateTime) mapper.registerModule(new JavaTimeModule()); mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); // 4. 保留原有的 DefaultTyping 配置 mapper.activateDefaultTyping( mapper.getPolymorphicTypeValidator(), ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY ); // 5. 创建序列化器 Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class); serializer.setObjectMapper(mapper); // 6. 配置 RedisCacheManager RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig() .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(serializer)) .entryTtl(Duration.ofHours(1)); return RedisCacheManager.builder(factory) .cacheDefaults(config) .build(); } }三、方案二:自定义反序列化器(更精细控制)
如果 Mixin 方式不生效,可以编写一个完整的PageImpl反序列化器。
1. 创建反序列化器
java
import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.deser.std.StdDeserializer; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import java.io.IOException; import java.util.List; public class PageImplDeserializer extends StdDeserializer<PageImpl<?>> { public PageImplDeserializer() { super(PageImpl.class); } @Override public PageImpl<?> deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { JsonNode node = p.getCodec().readTree(p); // 1. 解析 content JsonNode contentNode = node.get("content"); List<?> content = p.getCodec().treeToValue(contentNode, List.class); // 2. 解析 total JsonNode totalNode = node.get("total"); long total = totalNode != null ? totalNode.asLong() : 0L; // 3. 解析 pageable JsonNode pageableNode = node.get("pageable"); Pageable pageable; if (pageableNode == null || pageableNode.isNull()) { // 如果 pageable 不存在,从 page/size 构建 int page = node.has("page") ? node.get("page").asInt() : 0; int size = node.has("size") ? node.get("size").asInt() : 20; pageable = PageRequest.of(page, size); } else if (pageableNode.isTextual() && "INSTANCE".equals(pageableNode.asText())) { // 处理 Unpaged.INSTANCE pageable = Pageable.unpaged(); } else { // 从 pageable 对象解析 int page = pageableNode.has("pageNumber") ? pageableNode.get("pageNumber").asInt() : 0; int size = pageableNode.has("pageSize") ? pageableNode.get("pageSize").asInt() : 20; pageable = PageRequest.of(page, size); } return new PageImpl<>(content, pageable, total); } }2. 注册到 Redis ObjectMapper
java
@Configuration public class RedisConfig { @Bean public RedisCacheManager cacheManager(RedisConnectionFactory factory) { ObjectMapper mapper = new ObjectMapper(); // 注册 PageImpl 反序列化器 SimpleModule module = new SimpleModule(); module.addDeserializer(PageImpl.class, new PageImplDeserializer()); mapper.registerModule(module); // 其他配置... mapper.registerModule(new JavaTimeModule()); mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); mapper.activateDefaultTyping( mapper.getPolymorphicTypeValidator(), ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY ); Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class); serializer.setObjectMapper(mapper); RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig() .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(serializer)) .entryTtl(Duration.ofHours(1)); return RedisCacheManager.builder(factory) .cacheDefaults(config) .build(); } }四、方案三:直接修改 Redis 配置类(如果可访问)
如果 Redis 配置类是可访问的,直接在你现有的RedisConfig中添加 Mixin:
java
@Configuration public class RedisConfig { @Bean public RedisCacheManager cacheManager(RedisConnectionFactory factory) { // 获取现有的 ObjectMapper ObjectMapper mapper = new ObjectMapper(); // ✅ 添加这一行:注册 PageImpl 的 Mixin mapper.addMixIn(PageImpl.class, PageImplMixin.class); // 保留原有配置 mapper.activateDefaultTyping( mapper.getPolymorphicTypeValidator(), ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY ); // ... 其他配置 } }五、方案对比
| 方案 | 改动量 | 复杂度 | 推荐度 |
|---|---|---|---|
| Mixin | 小(新增1个类+1行配置) | 低 | ⭐⭐⭐⭐⭐ |
| 自定义反序列化器 | 中(新增1个类+注册逻辑) | 中 | ⭐⭐⭐⭐ |
| 修改现有 Redis 配置 | 最小(只加1行) | 最低 | ⭐⭐⭐⭐⭐ |
六、最终推荐
使用 Mixin 方案,这是最简洁、最优雅的方式:
java
// 1. 创建 Mixin 类(只需要注解,不需要实现) public abstract class PageImplMixin<T> { @JsonCreator public PageImplMixin( @JsonProperty("content") List<T> content, @JsonProperty("page") int page, @JsonProperty("size") int size, @JsonProperty("total") long total) {} } // 2. 在 Redis 配置中注册 mapper.addMixIn(PageImpl.class, PageImplMixin.class);这样:
✅ 不需要修改 Redis 序列化器的
activateDefaultTyping配置✅ 支持
PageImpl的反序列化✅ 不影响其他类型
✅ 改动量最小