Refine RedisSerializer implementations.

This commit polishes up method ordering, introduces Javadoc where missing and updates nullability annotations and argument names.

Closes #1097
This commit is contained in:
Mark Paluch
2023-09-21 14:46:14 +02:00
parent 468254d0fe
commit d4ba034b28
13 changed files with 162 additions and 142 deletions

View File

@@ -29,8 +29,8 @@ enum ByteArrayRedisSerializer implements RedisSerializer<byte[]> {
@Nullable
@Override
public byte[] serialize(@Nullable byte[] bytes) throws SerializationException {
return bytes;
public byte[] serialize(@Nullable byte[] value) throws SerializationException {
return value;
}
@Nullable

View File

@@ -31,7 +31,7 @@ class DefaultRedisElementReader<T> implements RedisElementReader<T> {
private final @Nullable RedisSerializer<T> serializer;
DefaultRedisElementReader(RedisSerializer<T> serializer) {
DefaultRedisElementReader(@Nullable RedisSerializer<T> serializer) {
this.serializer = serializer;
}

View File

@@ -35,7 +35,7 @@ class DefaultRedisElementWriter<T> implements RedisElementWriter<T> {
}
@Override
public ByteBuffer write(T value) {
public ByteBuffer write(@Nullable T value) {
if (serializer != null && (value == null || serializer.canSerialize(value.getClass()))) {
return ByteBuffer.wrap(serializer.serialize(value));
@@ -51,6 +51,5 @@ class DefaultRedisElementWriter<T> implements RedisElementWriter<T> {
throw new IllegalStateException(
String.format("Cannot serialize value of type %s without a serializer", value.getClass()));
}
}

View File

@@ -66,21 +66,6 @@ import com.fasterxml.jackson.databind.type.TypeFactory;
*/
public class GenericJackson2JsonRedisSerializer implements RedisSerializer<Object> {
/**
* Register {@link NullValueSerializer} in the given {@link ObjectMapper} with an optional
* {@code classPropertyTypeName}. This method should be called by code that customizes
* {@link GenericJackson2JsonRedisSerializer} by providing an external {@link ObjectMapper}.
*
* @param objectMapper the object mapper to customize.
* @param classPropertyTypeName name of the type property. Defaults to {@code @class} if {@literal null}/empty.
* @since 2.2
*/
public static void registerNullValueSerializer(ObjectMapper objectMapper, @Nullable String classPropertyTypeName) {
// Simply setting {@code mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS)} does not help here
// since we need the type hint embedded for deserialization using the default typing feature.
objectMapper.registerModule(new SimpleModule().addSerializer(new NullValueSerializer(classPropertyTypeName)));
}
private final JacksonObjectReader reader;
@@ -203,6 +188,22 @@ public class GenericJackson2JsonRedisSerializer implements RedisSerializer<Objec
.or("@class");
}
/**
* Register {@link NullValueSerializer} in the given {@link ObjectMapper} with an optional
* {@code classPropertyTypeName}. This method should be called by code that customizes
* {@link GenericJackson2JsonRedisSerializer} by providing an external {@link ObjectMapper}.
*
* @param objectMapper the object mapper to customize.
* @param classPropertyTypeName name of the type property. Defaults to {@code @class} if {@literal null}/empty.
* @since 2.2
*/
public static void registerNullValueSerializer(ObjectMapper objectMapper, @Nullable String classPropertyTypeName) {
// Simply setting {@code mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS)} does not help here
// since we need the type hint embedded for deserialization using the default typing feature.
objectMapper.registerModule(new SimpleModule().addSerializer(new NullValueSerializer(classPropertyTypeName)));
}
/**
* Gets the configured {@link ObjectMapper} used internally by this {@link GenericJackson2JsonRedisSerializer}
* to de/serialize {@link Object objects} as {@literal JSON}.
@@ -214,14 +215,14 @@ public class GenericJackson2JsonRedisSerializer implements RedisSerializer<Objec
}
@Override
public byte[] serialize(@Nullable Object source) throws SerializationException {
public byte[] serialize(@Nullable Object value) throws SerializationException {
if (source == null) {
if (value == null) {
return SerializationUtils.EMPTY_ARRAY;
}
try {
return writer.write(mapper, source);
return writer.write(mapper, value);
} catch (IOException cause) {
String message = String.format("Could not write JSON: %s", cause.getMessage());
throw new SerializationException(message, cause);

View File

@@ -22,7 +22,6 @@ import org.springframework.beans.BeansException;
import org.springframework.beans.TypeConverter;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.lang.Nullable;
@@ -42,8 +41,7 @@ public class GenericToStringSerializer<T> implements RedisSerializer<T>, BeanFac
private final Class<T> type;
private final Charset charset;
private Converter converter = new Converter(new DefaultConversionService());
private Converter converter;
public GenericToStringSerializer(Class<T> type) {
this(type, StandardCharsets.UTF_8);
@@ -55,20 +53,44 @@ public class GenericToStringSerializer<T> implements RedisSerializer<T>, BeanFac
this.type = type;
this.charset = charset;
this.converter = new Converter(DefaultConversionService.getSharedInstance());
}
/**
* Set the {@link ConversionService} to be used.
*
* @param conversionService the conversion service to be used, must not be {@literal null}.
*/
public void setConversionService(ConversionService conversionService) {
Assert.notNull(conversionService, "non null conversion service required");
Assert.notNull(conversionService, "ConversionService must not be null");
converter = new Converter(conversionService);
}
/**
* Set the {@link TypeConverter} to be used.
*
* @param typeConverter the conversion service to be used, must not be {@literal null}.
*/
public void setTypeConverter(TypeConverter typeConverter) {
Assert.notNull(typeConverter, "non null type converter required");
Assert.notNull(typeConverter, "TypeConverter must not be null");
converter = new Converter(typeConverter);
}
@Override
public byte[] serialize(@Nullable T value) {
if (value == null) {
return null;
}
String string = converter.convert(value, String.class);
return string.getBytes(charset);
}
@Override
public T deserialize(@Nullable byte[] bytes) {
@@ -81,29 +103,14 @@ public class GenericToStringSerializer<T> implements RedisSerializer<T>, BeanFac
}
@Override
public byte[] serialize(@Nullable T object) {
if (object == null) {
return null;
}
String string = converter.convert(object, String.class);
return string.getBytes(charset);
}
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
// TODO: This code can never happen...
if (converter == null && beanFactory instanceof ConfigurableBeanFactory) {
ConfigurableBeanFactory cFB = (ConfigurableBeanFactory) beanFactory;
ConversionService conversionService = cFB.getConversionService();
converter = (conversionService != null ? new Converter(conversionService)
: new Converter(cFB.getTypeConverter()));
}
// no-op
}
private class Converter {
private final ConversionService conversionService;
private final TypeConverter typeConverter;
private final static class Converter {
private final @Nullable ConversionService conversionService;
private final @Nullable TypeConverter typeConverter;
public Converter(ConversionService conversionService) {
this.conversionService = conversionService;
@@ -115,11 +122,11 @@ public class GenericToStringSerializer<T> implements RedisSerializer<T>, BeanFac
this.typeConverter = typeConverter;
}
@Nullable
<E> E convert(Object value, Class<E> targetType) {
if (conversionService != null) {
return conversionService.convert(value, targetType);
}
return typeConverter.convertIfNecessary(value, targetType);
return conversionService != null ? conversionService.convert(value, targetType)
: typeConverter.convertIfNecessary(value, targetType);
}
}
}

View File

@@ -126,32 +126,6 @@ public class Jackson2JsonRedisSerializer<T> implements RedisSerializer<T> {
this.javaType = javaType;
}
@SuppressWarnings("unchecked")
public T deserialize(@Nullable byte[] bytes) throws SerializationException {
if (SerializationUtils.isEmpty(bytes)) {
return null;
}
try {
return (T) this.reader.read(this.mapper, bytes, 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.writer.write(this.mapper, t);
} catch (Exception ex) {
throw new SerializationException("Could not write JSON: " + ex.getMessage(), ex);
}
}
/**
* Sets the {@code ObjectMapper} for this view. If not set, a default {@link ObjectMapper#ObjectMapper() ObjectMapper}
* is used.
@@ -171,6 +145,33 @@ public class Jackson2JsonRedisSerializer<T> implements RedisSerializer<T> {
this.mapper = mapper;
}
@Override
public byte[] serialize(@Nullable T value) throws SerializationException {
if (value == null) {
return SerializationUtils.EMPTY_ARRAY;
}
try {
return this.writer.write(this.mapper, value);
} catch (Exception ex) {
throw new SerializationException("Could not write JSON: " + ex.getMessage(), ex);
}
}
@Override
@SuppressWarnings("unchecked")
public T deserialize(@Nullable byte[] bytes) throws SerializationException {
if (SerializationUtils.isEmpty(bytes)) {
return null;
}
try {
return (T) this.reader.read(this.mapper, bytes, javaType);
} catch (Exception ex) {
throw new SerializationException("Could not read JSON: " + ex.getMessage(), ex);
}
}
/**
* Returns the Jackson {@link JavaType} for the specific class.
* <p>

View File

@@ -81,6 +81,20 @@ public class JdkSerializationRedisSerializer implements RedisSerializer<Object>
this.deserializer = RedisAssertions.requireNonNull(deserializer, "Deserializer must not be null");
}
@Override
public byte[] serialize(@Nullable Object value) {
if (value == null) {
return SerializationUtils.EMPTY_ARRAY;
}
try {
return serializer.convert(value);
} catch (Exception cause) {
throw new SerializationException("Cannot serialize", cause);
}
}
@Override
public Object deserialize(@Nullable byte[] bytes) {
@@ -94,18 +108,4 @@ public class JdkSerializationRedisSerializer implements RedisSerializer<Object>
throw new SerializationException("Cannot deserialize", cause);
}
}
@Override
public byte[] serialize(@Nullable Object object) {
if (object == null) {
return SerializationUtils.EMPTY_ARRAY;
}
try {
return serializer.convert(object);
} catch (Exception cause) {
throw new SerializationException("Cannot serialize", cause);
}
}
}

View File

@@ -84,6 +84,24 @@ public class OxmSerializer implements InitializingBean, RedisSerializer<Object>
Assert.state(unmarshaller != null, "non-null unmarshaller required");
}
@Override
public byte[] serialize(@Nullable Object value) throws SerializationException {
if (value == null) {
return SerializationUtils.EMPTY_ARRAY;
}
ByteArrayOutputStream stream = new ByteArrayOutputStream();
StreamResult result = new StreamResult(stream);
try {
marshaller.marshal(value, result);
} catch (Exception ex) {
throw new SerializationException("Cannot serialize object", ex);
}
return stream.toByteArray();
}
@Override
public Object deserialize(@Nullable byte[] bytes) throws SerializationException {
@@ -97,22 +115,4 @@ public class OxmSerializer implements InitializingBean, RedisSerializer<Object>
throw new SerializationException("Cannot deserialize bytes", ex);
}
}
@Override
public byte[] serialize(@Nullable Object t) throws SerializationException {
if (t == null) {
return SerializationUtils.EMPTY_ARRAY;
}
ByteArrayOutputStream stream = new ByteArrayOutputStream();
StreamResult result = new StreamResult(stream);
try {
marshaller.marshal(t, result);
} catch (Exception ex) {
throw new SerializationException("Cannot serialize object", ex);
}
return stream.toByteArray();
}
}

View File

@@ -17,6 +17,7 @@ package org.springframework.data.redis.serializer;
import java.nio.ByteBuffer;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
@@ -36,7 +37,7 @@ public interface RedisElementWriter<T> {
* @param element can be {@literal null}.
* @return the {@link ByteBuffer} representing {@code element} in its binary form.
*/
ByteBuffer write(T element);
ByteBuffer write(@Nullable T element);
/**
* Create new {@link RedisElementWriter} using given {@link RedisSerializer}.

View File

@@ -20,8 +20,9 @@ import org.springframework.util.ClassUtils;
/**
* Basic interface serialization and deserialization of Objects to byte arrays (binary data). It is recommended that
* implementations are designed to handle null objects/empty arrays on serialization and deserialization side. Note that
* Redis does not accept null keys or values but can return null replies (for non existing keys).
* implementations are designed to handle {@literal null} objects/empty arrays on serialization and deserialization
* side. Note that Redis does not accept {@literal null} keys or values but can return null replies (for non-existing
* keys).
*
* @author Mark Pollack
* @author Costin Leau
@@ -30,26 +31,8 @@ import org.springframework.util.ClassUtils;
public interface RedisSerializer<T> {
/**
* Serialize the given object to binary data.
*
* @param t object to serialize. Can be {@literal null}.
* @return the equivalent binary data. Can be {@literal null}.
*/
@Nullable
byte[] serialize(@Nullable T t) throws SerializationException;
/**
* Deserialize an object from the given binary data.
*
* @param bytes object binary representation. Can be {@literal null}.
* @return the equivalent object instance. Can be {@literal null}.
*/
@Nullable
T deserialize(@Nullable byte[] bytes) throws SerializationException;
/**
* Obtain a {@link RedisSerializer} using java serialization.<br />
* <strong>Note:</strong> Ensure that your domain objects are actually {@link java.io.Serializable serializable}.
* Obtain a {@link RedisSerializer} using java serialization. <strong>Note:</strong> Ensure that your domain objects
* are actually {@link java.io.Serializable serializable}.
*
* @return never {@literal null}.
* @since 2.1
@@ -59,7 +42,7 @@ public interface RedisSerializer<T> {
}
/**
* Obtain a {@link RedisSerializer} using java serialization with the given {@link ClassLoader}.<br />
* Obtain a {@link RedisSerializer} using java serialization with the given {@link ClassLoader}.
* <strong>Note:</strong> Ensure that your domain objects are actually {@link java.io.Serializable serializable}.
*
* @param classLoader the {@link ClassLoader} to use for deserialization. Can be {@literal null}.
@@ -102,10 +85,39 @@ public interface RedisSerializer<T> {
return ByteArrayRedisSerializer.INSTANCE;
}
/**
* Serialize the given object to binary data.
*
* @param value object to serialize. Can be {@literal null}.
* @return the equivalent binary data. Can be {@literal null}.
*/
@Nullable
byte[] serialize(@Nullable T value) throws SerializationException;
/**
* Deserialize an object from the given binary data.
*
* @param bytes object binary representation. Can be {@literal null}.
* @return the equivalent object instance. Can be {@literal null}.
*/
@Nullable
T deserialize(@Nullable byte[] bytes) throws SerializationException;
/**
* Check whether the given value {@code type} can be serialized by this serializer.
*
* @param type the value type.
* @return {@code true} if the value type can be serialized; {@code false} otherwise.
*/
default boolean canSerialize(Class<?> type) {
return ClassUtils.isAssignable(getTargetType(), type);
}
/**
* Return the serializer target type.
*
* @return the serializer target type.
*/
default Class<?> getTargetType() {
return Object.class;
}

View File

@@ -81,13 +81,13 @@ public class StringRedisSerializer implements RedisSerializer<String> {
}
@Override
public String deserialize(@Nullable byte[] bytes) {
return (bytes == null ? null : new String(bytes, charset));
public byte[] serialize(@Nullable String value) {
return (value == null ? null : value.getBytes(charset));
}
@Override
public byte[] serialize(@Nullable String string) {
return (string == null ? null : string.getBytes(charset));
public String deserialize(@Nullable byte[] bytes) {
return (bytes == null ? null : new String(bytes, charset));
}
@Override