WebFlux 响应式定制 Redis 对指定数据类型(例如 ObjectId、LocalDate)的序列化与反序列化配置

背景

在对 BSON 的 ObjectId 类型与 Java 的 LocalDate 类型进行序列化与反序列化时,Redis 出现了无法对 ObjectIdLocalDate 进行序列化的报错

配置 ReactiveRedisTemplate<String, Object>

于是作者决定在 Redis 的配置类中ReactiveRedisTemplate<String, Object> 进行定制,在阅读源码后发现 ReactiveRedisTemplate 的构造函数中可以传入一个 RedisSerializationContext<String, Object> 对象,对 Redis 序列化方式进行定制:

@Bean
public ReactiveRedisTemplate<String, Object> reactiveRedisTemplate(
        ReactiveRedisConnectionFactory reactiveRedisConnectionFactory,
        RedisSerializationContext<String, Object> redisSerializationContext) {
    
     // 配置 RedisSerializationContext
    return new ReactiveRedisTemplate<>(reactiveRedisConnectionFactory, redisSerializationContext);
}

配置 RedisSerializationContext<String, Object>

查看 RedisSerializationContext<K, V> 接口对源码后,发现其内部有一个静态接口 RedisSerializationContextBuilder<K, V>,专门用于构建 RedisSerializationContext<K, V> 对象,其 key()value()hashKey()hashValue() 等方法支持对序列化方式进行定制化:

/**
 * Builder for {@link RedisSerializationContext}.
 *
 * @author Mark Paluch
 * @author Christoph Strobl
 */
interface RedisSerializationContextBuilder<K, V> {
    
     ... }

于是RedisSerializationContext<String, Object> 进行定制,使用自定义的序列化方式对 Redis 的 value 与 hashValue 进行特制的序列化:

@Bean
public RedisSerializationContext<String, Object> redisSerializationContext() {
    
    
    RedisSerializationContext.RedisSerializationContextBuilder<String, Object> builder = RedisSerializationContext.newSerializationContext();

    builder.key(StringRedisSerializer.UTF_8);
    builder.value(serializer()); // 设置自定义序列化

    builder.hashKey(StringRedisSerializer.UTF_8);
    builder.hashValue(serializer()); // 设置自定义序列化

    return builder.build();
}

发现 RedisSerializationContext.RedisSerializationContextBuilder<String, Object> 接口的 value(...)hashValue(...) 方法需要传入一个 RedisSerializer 对象,根据之前的项目经验,决定对其实现类 Jackson2JsonRedisSerializer 进行定制

定制 Jackson2JsonRedisSerializerObjectMapper

Jackson2JsonRedisSerializer 类中,发现有一个 ObjectMapper 类型的字段,根据其在 deserialize 方法与 serialize 方法中被调用的方式,不难猜出其负责对象的映射,故选择对 ObjectMapper 也进行定制化:

@SuppressWarnings("unchecked")
public T deserialize(@Nullable byte[] bytes) throws SerializationException {
    
    

   if (SerializationUtils.isEmpty(bytes)) {
    
    
      return null;
   }
   try {
    
    
      return (T) this.objectMapper.readValue(bytes, 0, bytes.length, javaType);
   } catch (Exception ex) {
    
    
      throw new SerializationException("Could not read JSON: " + ex.getMessage(), ex);
   }
}

@Override
public byte[] serialize(@Nullable Object t) throws SerializationException {
    
    

   if (t == null) {
    
    
      return SerializationUtils.EMPTY_ARRAY;
   }
   try {
    
    
      return this.objectMapper.writeValueAsBytes(t);
   } catch (Exception ex) {
    
    
      throw new SerializationException("Could not write JSON: " + ex.getMessage(), ex);
   }
}

ObjectMapper 中发现了一个 registerModule 方法,支持定制序列化器与反序列化器

/**
 * Method for registering a module that can extend functionality
 * provided by this mapper; for example, by adding providers for
 * custom serializers and deserializers.
 * 
 * @param module Module to register
 */
public ObjectMapper registerModule(Module module) {
    
     ... }

将定制的 Module 注册进 ObjectMapper

使用 JavaTimeModule 序列化 LocalDate

Module 是一个 abstruct class,在其子类中,找到了 JavaTimeModule 类,其支持对 LocalDate 等时间类进行序列化,故将 JavaTimeModule 对象注册进 ObjectMapper 对象:

objectMapper.registerModule(new JavaTimeModule());

定制 SimpleModule 序列化 ObjectId

但是未找到对 BSON 的 ObjectId 类型进行序列化的 Module,于是构造一个 SimpleModule 对象调用 addSerializer(...)addDeserializer(...) 方法进行序列化与反序列化的定制(在此使用 ObjectId 的十六进制形式的字符串进行序列化),并将其注册进 ObjectMapper 对象:

SimpleModule objectIdModule = new SimpleModule("ObjectIdModule");
objectIdModule.addSerializer(ObjectId.class, new JsonSerializer<ObjectId>() {
    
    
    @Override
    public void serialize(ObjectId objectId, JsonGenerator jsonGenerator, 
                          SerializerProvider serializerProvider) throws IOException {
    
    
        jsonGenerator.writeString(objectId.toString());
    }
});
objectIdModule.addDeserializer(ObjectId.class, new JsonDeserializer<ObjectId>() {
    
    
    @Override
    public ObjectId deserialize(JsonParser jsonParser, 
                                DeserializationContext deserializationContext) throws IOException {
    
    
        return new ObjectId(jsonParser.readValueAs(String.class));
    }
});
objectMapper.registerModule(objectIdModule);

至此,完成 Redis 对 ObjectIdLocalDate 类进行定制的工作。

Redis 配置文件

package cn.tzq0301.auth.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.bson.types.ObjectId;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory;
import org.springframework.data.redis.core.ReactiveRedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.io.IOException;

/**
 * @author tzq0301
 * @version 1.0
 */
@SpringBootConfiguration
public class RedisConfig {
    
    
    public static final String PROJECT_NAMESPACE_PREFIX = "pcs:";

    public static final String USER_NAMESPACE_PREFIX = PROJECT_NAMESPACE_PREFIX + "user:";

    @Bean
    public ReactiveRedisTemplate<String, Object> reactiveRedisTemplate(
            ReactiveRedisConnectionFactory reactiveRedisConnectionFactory,
            RedisSerializationContext<String, Object> redisSerializationContext) {
    
    
        return new ReactiveRedisTemplate<>(reactiveRedisConnectionFactory, redisSerializationContext);
    }

    @Bean
    public RedisSerializationContext<String, Object> redisSerializationContext() {
    
    
        RedisSerializationContext.RedisSerializationContextBuilder<String, Object> builder = RedisSerializationContext.newSerializationContext();

        builder.key(StringRedisSerializer.UTF_8);
        builder.value(serializer());

        builder.hashKey(StringRedisSerializer.UTF_8);
        builder.hashValue(serializer());

        return builder.build();
    }

    private Jackson2JsonRedisSerializer<Object> serializer() {
    
    
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
        ObjectMapper objectMapper = new ObjectMapper();

        // ObjectId
        SimpleModule objectIdModule = new SimpleModule("ObjectIdModule");
        objectIdModule.addSerializer(ObjectId.class, new JsonSerializer<ObjectId>() {
    
    
            @Override
            public void serialize(ObjectId objectId, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
    
    
                jsonGenerator.writeString(objectId.toString());
            }
        });
        objectIdModule.addDeserializer(ObjectId.class, new JsonDeserializer<ObjectId>() {
    
    
            @Override
            public ObjectId deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
    
    
                return new ObjectId(jsonParser.readValueAs(String.class));
            }
        });
        objectMapper.registerModule(objectIdModule);

        // LocalDate
        objectMapper.registerModule(new JavaTimeModule());

        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
        return jackson2JsonRedisSerializer;
    }
}

猜你喜欢

转载自blog.csdn.net/m0_46261993/article/details/123436148