背景
在对 BSON 的 ObjectId
类型与 Java 的 LocalDate
类型进行序列化与反序列化时,Redis 出现了无法对 ObjectId
、LocalDate
进行序列化的报错。
配置 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
进行定制。
定制 Jackson2JsonRedisSerializer
的 ObjectMapper
在 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 对 ObjectId
与 LocalDate
类进行定制的工作。
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;
}
}