【Spring Boot 3 + fastjson2】更改RedisTemplate的序列化策略

1,097 阅读2分钟

环境:jdk17、Spring Boot 3.1.3、fastjson2 2.0.41

pom依赖

 <properties>
     <java.version>17</java.version>
     <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
     <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
     <spring-boot.version>3.1.3</spring-boot.version>
     <fastjson2.version>2.0.41</fastjson2.version>
 <properties>
 <dependencies>
     <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-web</artifactId>
     </dependency>
     <dependency>
         <groupId>com.alibaba.fastjson2</groupId>
         <artifactId>fastjson2-extension-spring6</artifactId>
         <version>${fastjson2.version}</version>
     </dependency>
     <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-data-redis</artifactId>
     </dependency>
 </dependencies>
 <dependencyManagement>
     <dependencies>
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-parent</artifactId>
             <version>${spring-boot.version}</version>
             <type>pom</type>
             <scope>import</scope>
         </dependency>
     </dependencies>
 </dependencyManagement>

Jackson2Json

在查找资料的过程中,RedisTemplate的序列化策略大部分是用jackson去实现的,这里我也在这里实现以下

 import com.fasterxml.jackson.annotation.JsonAutoDetect;
 import com.fasterxml.jackson.annotation.JsonTypeInfo;
 import com.fasterxml.jackson.annotation.PropertyAccessor;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
 import jakarta.annotation.Resource;
 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 RedisTemplateConfig {
 ​
     @Resource
     private RedisConnectionFactory redisConnectionFactory;
 ​
     @Bean
     public RedisTemplate<String, Object> redisTemplate() {
         // 自定义 String Object
         RedisTemplate<String, Object> template = new RedisTemplate<>();
 ​
         // ObjectMapper 转译
         ObjectMapper objectMapper = createObjectMapper();
 ​
         // Json 序列化配置
         Jackson2JsonRedisSerializer<Object> objectJackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(objectMapper, Object.class);
 ​
         // String 的序列化
         StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
 ​
         // key 采用String的序列化方式
         template.setKeySerializer(stringRedisSerializer);
         // hash 的key也采用 String 的序列化方式
         template.setHashKeySerializer(stringRedisSerializer);
         // value 序列化方式采用 jackson
         template.setValueSerializer(objectJackson2JsonRedisSerializer);
         // hash 的 value 采用 jackson
         template.setHashValueSerializer(objectJackson2JsonRedisSerializer);
         template.setConnectionFactory(redisConnectionFactory);
         template.afterPropertiesSet();
 ​
         return template;
     }
 ​
     private ObjectMapper createObjectMapper() {
         ObjectMapper mapper = new ObjectMapper();
         mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
         mapper.activateDefaultTyping(
                 LaissezFaireSubTypeValidator.instance,
                 ObjectMapper.DefaultTyping.NON_FINAL,
                 JsonTypeInfo.As.WRAPPER_ARRAY
         );
         return mapper;
     }
 }

如果key不采用stringRedisSerializer方式的话,存储的key会有双引号(“”)而且无法通过key去get到value

Tips:

new Jackson2JsonRedisSerializer<>(objectMapper, Object.class)这个方法是3.0才有的,不是3.0的话使用objectJackson2JsonRedisSerializer.setObjectMapper(objectMapper)才行,到3.0objectJackson2JsonRedisSerializer.setObjectMapper(objectMapper)这个方法已经过时了,所以采用new Jackson2JsonRedisSerializer<>(objectMapper, Object.class)这个方法比较好。

fastjson2

查看Jackson2JsonRedisSerializer的源码知道这个类是实现了RedisSerializer<>的,所以我们也要实现。

先定义一个Fastjson2RedisSerializer并实现RedisSerializer<>:

 public class FastJson2RedisSerializer<T> implements RedisSerializer<T> {}

然后去实现方法,这里加快速度,直接展示成品

 import com.alibaba.fastjson2.JSON;
 import com.alibaba.fastjson2.JSONReader;
 import com.alibaba.fastjson2.JSONWriter;
 import com.alibaba.fastjson2.filter.Filter;
 import org.apache.commons.lang3.ArrayUtils;
 import org.springframework.data.redis.serializer.RedisSerializer;
 import org.springframework.data.redis.serializer.SerializationException;
 ​
 public class FastJson2RedisSerializer<T> implements RedisSerializer<T> {
 ​
     static final Filter AUTO_TYPE_FILTER = JSONReader.autoTypeFilter(
             // 按需加上需要支持自动类型的类名前缀,范围越小越安全
             "com.***.***"
     );
 ​
     private final Class<T> clazz;
 ​
     public FastJson2RedisSerializer(Class<T> clazz) {
         super();
         this.clazz = clazz;
     }
 ​
     @Override
     public byte[] serialize(T t) throws SerializationException {
         if (t == null) {
             return new byte[0];
         }
         return JSON.toJSONBytes(t, JSONWriter.Feature.WriteClassName);
     }
 ​
     @Override
     public T deserialize(byte[] bytes) throws SerializationException {
         if (ArrayUtils.isEmpty(bytes)) {
             return null;
         }
 ​
         return JSON.parseObject(bytes, clazz, AUTO_TYPE_FILTER);
     }
 }

然后再去配置类中实现我们的fastjson2的序列化配置

 import jakarta.annotation.Resource;
 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.StringRedisSerializer;
 ​
 @Configuration
 public class RedisTemplateConfig {
 ​
     @Resource
     private RedisConnectionFactory redisConnectionFactory;
     
         @Bean
     public RedisTemplate<Object, Object> redisTemplate() {
         RedisTemplate<Object, Object> template = new RedisTemplate<>();
         FastJson2RedisSerializer<Object> fastJson2RedisSerializer = new FastJson2RedisSerializer<>(Object.class);
         StringRedisSerializer serializer = new StringRedisSerializer();
         template.setConnectionFactory(redisConnectionFactory);
         template.setKeySerializer(serializer);
         template.setHashKeySerializer(serializer);
         template.setValueSerializer(fastJson2RedisSerializer);
         template.setHashValueSerializer(fastJson2RedisSerializer);
         template.afterPropertiesSet();
         return template;
     }
 }

优化

FastJson2RedisSerializer

 import com.alibaba.fastjson2.JSON;
 import com.alibaba.fastjson2.JSONReader;
 import com.alibaba.fastjson2.JSONWriter;
 import com.alibaba.fastjson2.filter.Filter;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.ArrayUtils;
 import org.springframework.data.redis.serializer.RedisSerializer;
 import org.springframework.data.redis.serializer.SerializationException;
 ​
 import java.util.Objects;
 ​
 @Slf4j
 public class FastJson2RedisSerializer<T> implements RedisSerializer<T> {
 ​
     static final Filter AUTO_TYPE_FILTER = JSONReader.autoTypeFilter(
             // 按需加上需要支持自动类型的类名前缀,范围越小越安全
             "com.***.***"
     );
 ​
     private final Class<T> clazz;
 ​
     public FastJson2RedisSerializer(Class<T> clazz) {
         super();
         this.clazz = clazz;
     }
 ​
     @Override
     public byte[] serialize(T t) throws SerializationException {
         if (Objects.isNull(t)) {
             return new byte[0];
         }
         try {
             return JSON.toJSONBytes(t, JSONWriter.Feature.WriteClassName);
         } catch (Exception e) {
             log.error("Fastjson2 序列化错误:{}", e.getMessage());
             throw new SerializationException("无法序列化: " + e.getMessage(), e);
         }
 ​
     }
 ​
     @Override
     public T deserialize(byte[] bytes) throws SerializationException {
         if (ArrayUtils.isEmpty(bytes)) {
             return null;
         }
         try {
             return JSON.parseObject(bytes, clazz, AUTO_TYPE_FILTER);
         } catch (Exception e) {
             log.error("Fastjson2 反序列化错误:{}", e.getMessage());
             throw new SerializationException("无法反序列化: " + e.getMessage(), e);
         }
 ​
     }
 }