java spring RedisTemplate几种序列化方式对比分析

461 阅读5分钟

1.提供的几种序列化方式

image.png

2.详细对比(只看spring封装的,fastjson提供的序列化不考虑使用)

2.1 ByteArrayRedisSerializer

2.1.1 序列化源码

通过枚举实现的单例,仅可存储byte[]

enum ByteArrayRedisSerializer implements RedisSerializer<byte[]> {

    INSTANCE;

    @Nullable
    @Override
    public byte[] serialize(@Nullable byte[] bytes) throws SerializationException {
       return bytes;
    }

    @Nullable
    @Override
    public byte[] deserialize(@Nullable byte[] bytes) throws SerializationException {
       return bytes;
    }
}

2.1.2 测试详情



@Bean
public RedisTemplate<String, Object> objectRedisTemplate(@Qualifier(value = "redisConnectionFactory") RedisConnectionFactory factory) {
    RedisTemplate<String, Object>
            redisTemplate = new RedisTemplate<>();
    redisTemplate.setConnectionFactory(factory);
    redisTemplate.setKeySerializer(new StringRedisSerializer());
    redisTemplate.setValueSerializer(RedisSerializer.byteArray());
    return redisTemplate;
}

@Test
public void test() {
    byte[] bytes = new byte[1];
    bytes[0] = 1;
    ValueOperations<String, byte[]> stringObjectValueOperations = objectRedisTemplate.opsForValue();
    stringObjectValueOperations.set("test", bytes);
    byte[] object = stringObjectValueOperations.get("test");
    System.out.println(object[0]);//成功输出1 }
}

2.1.3 小结

  1. 仅支持byte[],其他数据类型会报转换异常
  2. 存入的数据可读性差

2.2 GenericJackson2JsonRedisSerializer

2.2.1 序列化源码

通用Jackson2转换,通过在序列化的值中设置类路径信息,达到通用转换的结果

mapper.enableDefaultTyping(DefaultTyping.NON_FINAL, As.PROPERTY);
private final ObjectMapper mapper;

@Override
public byte[] serialize(@Nullable Object source) throws SerializationException {
    if (source == null) {
        return SerializationUtils.EMPTY_ARRAY;
    }
    try {
        return mapper.writeValueAsBytes(source);
    } catch (JsonProcessingException e) {
        throw new SerializationException("Could not write JSON: " + e.getMessage(), e);
    }
}

@Nullable
public <T> T deserialize(@Nullable byte[] source, Class<T> type) throws SerializationException {
    Assert.notNull(type,
            "Deserialization type must not be null! Please provide Object.class to make use of Jackson2 default typing.");
    if (SerializationUtils.isEmpty(source)) {
        return null;
    }
    try {
        return mapper.readValue(source, type);
    } catch (Exception ex) {
        throw new SerializationException("Could not read JSON: " + ex.getMessage(), ex);
    }
}

2.2.2 测试详情

@Bean
public RedisTemplate<String, Object> objectRedisTemplate(@Qualifier(value = "redisConnectionFactory") RedisConnectionFactory factory) {
    RedisTemplate<String,Object> redisTemplate = new RedisTemplate<>();
    redisTemplate.setConnectionFactory(factory);
    redisTemplate.setKeySerializer(new StringRedisSerializer());
    redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
    return redisTemplate;
}


@Test
public void test() {
    RedisObj redisObj = new RedisObj();
    redisObj.setName("张三");
    redisObj.setId(1);
    ValueOperations<String, RedisObj> stringObjectValueOperations = objectRedisTemplate.opsForValue();
    stringObjectValueOperations.set("test", redisObj);
    RedisObj res = stringObjectValueOperations.get("test");
    System.out.println(res);
}

存入的为json数据,并记录了存入对象的路径

image.png

当删除对象中某个成员变量,会导致deserialize失败

image.png

解决方案:自定义ObjectMapper

@Bean
public RedisTemplate<String, Object> objectRedisTemplateOne(@Qualifier(value = "redisConnectionFactory") RedisConnectionFactory factory) {
    RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
    redisTemplate.setConnectionFactory(factory);
    redisTemplate.setKeySerializer(new StringRedisSerializer());
    ObjectMapper objectMapper = new ObjectMapper();
    //遇到未知属性不处理
    objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    //在json中写入class信息,用于反序列化
    objectMapper.activateDefaultTyping(objectMapper.getPolymorphicTypeValidator(), ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
    redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer(objectMapper));
    return redisTemplate;
}

2.2.3 小结

  1. 不能随意修改类名称,路径,以及删除对象中的成员变量,会导致序列化失败
  2. 存入的数据为json,可读性高

2.3 GenericToStringSerializer

2.3.1 序列化源码

使用Converter将对象转换成string

@Override
public T deserialize(@Nullable byte[] bytes) {

    if (bytes == null) {
        return null;
    }

    String string = new String(bytes, charset);
    return converter.convert(string, type);
}

@Override
public byte[] serialize(@Nullable T object) {
    if (object == null) {
        return null;
    }
    String string = converter.convert(object, String.class);
    return string.getBytes(charset);
}

支持转换的类型

public static void addDefaultConverters(ConverterRegistry converterRegistry) {
    addScalarConverters(converterRegistry);
    addCollectionConverters(converterRegistry);

    converterRegistry.addConverter(new ByteBufferConverter((ConversionService) converterRegistry));
    converterRegistry.addConverter(new StringToTimeZoneConverter());
    converterRegistry.addConverter(new ZoneIdToTimeZoneConverter());
    converterRegistry.addConverter(new ZonedDateTimeToCalendarConverter());

    converterRegistry.addConverter(new ObjectToObjectConverter());
    converterRegistry.addConverter(new IdToEntityConverter((ConversionService) converterRegistry));
    converterRegistry.addConverter(new FallbackObjectToStringConverter());
    converterRegistry.addConverter(new ObjectToOptionalConverter((ConversionService) converterRegistry));
}

private static void addScalarConverters(ConverterRegistry converterRegistry) {
    converterRegistry.addConverterFactory(new NumberToNumberConverterFactory());

    converterRegistry.addConverterFactory(new StringToNumberConverterFactory());
    converterRegistry.addConverter(Number.class, String.class, new ObjectToStringConverter());

    converterRegistry.addConverter(new StringToCharacterConverter());
    converterRegistry.addConverter(Character.class, String.class, new ObjectToStringConverter());

    converterRegistry.addConverter(new NumberToCharacterConverter());
    converterRegistry.addConverterFactory(new CharacterToNumberFactory());

    converterRegistry.addConverter(new StringToBooleanConverter());
    converterRegistry.addConverter(Boolean.class, String.class, new ObjectToStringConverter());

    converterRegistry.addConverterFactory(new StringToEnumConverterFactory());
    converterRegistry.addConverter(new EnumToStringConverter((ConversionService) converterRegistry));

    converterRegistry.addConverterFactory(new IntegerToEnumConverterFactory());
    converterRegistry.addConverter(new EnumToIntegerConverter((ConversionService) converterRegistry));

    converterRegistry.addConverter(new StringToLocaleConverter());
    converterRegistry.addConverter(Locale.class, String.class, new ObjectToStringConverter());

    converterRegistry.addConverter(new StringToCharsetConverter());
    converterRegistry.addConverter(Charset.class, String.class, new ObjectToStringConverter());

    converterRegistry.addConverter(new StringToCurrencyConverter());
    converterRegistry.addConverter(Currency.class, String.class, new ObjectToStringConverter());

    converterRegistry.addConverter(new StringToPropertiesConverter());
    converterRegistry.addConverter(new PropertiesToStringConverter());

    converterRegistry.addConverter(new StringToUUIDConverter());
    converterRegistry.addConverter(UUID.class, String.class, new ObjectToStringConverter());
}

public static void addCollectionConverters(ConverterRegistry converterRegistry) {
    ConversionService conversionService = (ConversionService) converterRegistry;

    converterRegistry.addConverter(new ArrayToCollectionConverter(conversionService));
    converterRegistry.addConverter(new CollectionToArrayConverter(conversionService));

    converterRegistry.addConverter(new ArrayToArrayConverter(conversionService));
    converterRegistry.addConverter(new CollectionToCollectionConverter(conversionService));
    converterRegistry.addConverter(new MapToMapConverter(conversionService));

    converterRegistry.addConverter(new ArrayToStringConverter(conversionService));
    converterRegistry.addConverter(new StringToArrayConverter(conversionService));

    converterRegistry.addConverter(new ArrayToObjectConverter(conversionService));
    converterRegistry.addConverter(new ObjectToArrayConverter(conversionService));

    converterRegistry.addConverter(new CollectionToStringConverter(conversionService));
    converterRegistry.addConverter(new StringToCollectionConverter(conversionService));

    converterRegistry.addConverter(new CollectionToObjectConverter(conversionService));
    converterRegistry.addConverter(new ObjectToCollectionConverter(conversionService));

    converterRegistry.addConverter(new StreamConverter(conversionService));
}

2.3.2 小结

  1. 不同的对象需要写不同的转换代码,复杂度高,不建议使用

2.4Jackson2JsonRedisSerializer

2.4.1 序列化源码

可以使用Jackson和Jackson的Databind ObjectMapper读取和写入JSON。此转换器可用于绑定到类型化的bean或非类型化的HashMap实例

public class Jackson2JsonRedisSerializer<T> implements RedisSerializer<T> {

    public static final Charset  DEFAULT_CHARSET = StandardCharsets.UTF_8;
    //需要转换的Class对象
    private final       JavaType javaType;

    private ObjectMapper objectMapper = new ObjectMapper();

    public Jackson2JsonRedisSerializer(Class<T> type) {
        this.javaType = getJavaType(type);
    }

    public Jackson2JsonRedisSerializer(JavaType javaType) {
        this.javaType = javaType;
    }

    @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);
        }
    }


    public void setObjectMapper(ObjectMapper objectMapper) {

        Assert.notNull(objectMapper, "'objectMapper' must not be null");
        this.objectMapper = objectMapper;
    }


    protected JavaType getJavaType(Class<?> clazz) {
        return TypeFactory.defaultInstance().constructType(clazz);
    }
}

2.4.2 测试详情

测试代码

@Bean
public RedisTemplate<String, Object> objectRedisTemplateOne(@Qualifier(value = "redisConnectionFactory") RedisConnectionFactory factory) {
    RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
    redisTemplate.setConnectionFactory(factory);
    redisTemplate.setKeySerializer(new StringRedisSerializer());
    redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<>(RedisObj.class));
    return redisTemplate;
}



@Test
public void test() {
    RedisConfig.RedisObj redisObj = new RedisConfig.RedisObj();
    redisObj.setName("张三");
    redisObj.setId(2);
    ValueOperations<String, RedisConfig.RedisObj> stringObjectValueOperations = objectRedisTemplate.opsForValue();
    stringObjectValueOperations.set("test", redisObj);
    RedisConfig.RedisObj res = stringObjectValueOperations.get("test");
    System.out.println(res);
}

存入的为json数据,没有存入对象的路径

image.png 当删除对象中某个成员变量,同样会导致deserialize失败

image.png 解决方案:自定义ObjectMapper

@Bean
public RedisTemplate<String, Object> objectRedisTemplateOne(@Qualifier(value = "redisConnectionFactory") RedisConnectionFactory factory) {
    RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
    redisTemplate.setConnectionFactory(factory);
    redisTemplate.setKeySerializer(new StringRedisSerializer());
    Jackson2JsonRedisSerializer<RedisObj> redisObjJackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(RedisObj.class);
    ObjectMapper objectMapper = new ObjectMapper();
    //遇到未知属性不处理
    objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    redisObjJackson2JsonRedisSerializer.setObjectMapper(objectMapper);
    redisTemplate.setValueSerializer(redisObjJackson2JsonRedisSerializer);
    return redisTemplate;
}

2.4.3 小结

  1. 需要在@Bean注入的时候设置序列化对象,通用性较差
  2. 和GenericJackson2JsonRedisSerializer的区别
//GenericJackson2JsonRedisSerializer在源码中对ObjectMapper设置了通用类型处理,不需要在单独设置序列化的对象,需要考虑修改包名,路径导致的序列化失败问题 
mapper.enableDefaultTyping(DefaultTyping.NON_FINAL, As.PROPERTY); 
// 而Jackson2JsonRedisSerializer需要在设置序列化方式的时候设置具体的对象,在实际开发中这是难以接受的 
new Jackson2JsonRedisSerializer<>(RedisObj.class);

2.5 JdkSerializationRedisSerializer

2.5.1 序列化源码

public class JdkSerializationRedisSerializer implements RedisSerializer<Object> {

    private final Converter<Object, byte[]> serializer;
    private final Converter<byte[], Object> deserializer;

    /**
     * Creates a new {@link JdkSerializationRedisSerializer} using the default class loader.
     */
    public JdkSerializationRedisSerializer() {
        this(new SerializingConverter(), new DeserializingConverter());
    }

    /**
     * Creates a new {@link JdkSerializationRedisSerializer} using a {@link ClassLoader}.
     *
     * @param classLoader the {@link ClassLoader} to use for deserialization. Can be {@literal null}.
     * @since 1.7
     */
    public JdkSerializationRedisSerializer(@Nullable ClassLoader classLoader) {
        this(new SerializingConverter(), new DeserializingConverter(classLoader));
    }

    /**
     * Creates a new {@link JdkSerializationRedisSerializer} using a {@link Converter converters} to serialize and
     * deserialize objects.
     *
     * @param serializer must not be {@literal null}
     * @param deserializer must not be {@literal null}
     * @since 1.7
     */
    public JdkSerializationRedisSerializer(Converter<Object, byte[]> serializer, Converter<byte[], Object> deserializer) {

        Assert.notNull(serializer, "Serializer must not be null!");
        Assert.notNull(deserializer, "Deserializer must not be null!");

        this.serializer = serializer;
        this.deserializer = deserializer;
    }

    public Object deserialize(@Nullable byte[] bytes) {

        if (SerializationUtils.isEmpty(bytes)) {
            return null;
        }

        try {
            return deserializer.convert(bytes);
        } catch (Exception ex) {
            throw new SerializationException("Cannot deserialize", ex);
        }
    }

    @Override
    public byte[] serialize(@Nullable Object object) {
        if (object == null) {
            return SerializationUtils.EMPTY_ARRAY;
        }
        try {
            return serializer.convert(object);
        } catch (Exception ex) {
            throw new SerializationException("Cannot serialize", ex);
        }
    }
}

2.5.2 测试详情

@Bean
public RedisTemplate<String, Object> objectRedisTemplateOne(@Qualifier(value = "redisConnectionFactory") RedisConnectionFactory factory) {
    RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
    redisTemplate.setConnectionFactory(factory);
    redisTemplate.setKeySerializer(new StringRedisSerializer());
    redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer());
    return redisTemplate;
}


@Test
public void test() {
    RedisConfig.RedisObj redisObj = new RedisConfig.RedisObj();
    redisObj.setName("张三");
    redisObj.setId(2);
    ValueOperations<String, RedisConfig.RedisObj> stringObjectValueOperations = objectRedisTemplate.opsForValue();
    stringObjectValueOperations.set("test", redisObj);
    RedisConfig.RedisObj res = stringObjectValueOperations.get("test");
    System.out.println(res);
}

存入的为JDK序列化后的数据,可读性较差

image.png 修改对象,会导致序列化失败

image.png

2.5.3 小结

  1. 修改对象后,无法序列化
  2. 序列化后的数据可读性差
  3. 需要占用较大的存储空间

2.6 StringRedisSerializer

2.6.1 序列化源码

提供简单String到byte[]的序列化程序

@Override
public String deserialize(@Nullable byte[] bytes) {
    return (bytes == null ? null : new String(bytes, charset));
}

/*
 * (non-Javadoc)
 * @see org.springframework.data.redis.serializer.RedisSerializer#serialize(java.lang.Object)
 */
@Override
public byte[] serialize(@Nullable String string) {
    return (string == null ? null : string.getBytes(charset));
}

2.6.2 测试详情

/**
 spring 已经提供对于操作string类型的StringRedisTemplate,直接使用即可
 **/
@Bean
public StringRedisTemplate objectRedisTemplateOne(@Qualifier(value = "redisConnectionFactory") RedisConnectionFactory factory) {
    StringRedisTemplate redisTemplate = new StringRedisTemplate();
    redisTemplate.setConnectionFactory(factory);
    return redisTemplate;
}


@Test
public void test() {
    RedisConfig.RedisObj redisObj = new RedisConfig.RedisObj();
    redisObj.setName("张三");
    redisObj.setId(2);
    ValueOperations<String, String> stringValueOperations = stringRedisTemplate.opsForValue();
    stringValueOperations.set("test", JSON.toJSONString(redisObj));
    String res = stringValueOperations.get("test");
    System.out.println(res);
}

image.png

2.6.3 小结

  1. 存入字符串,扩展性较强,可读性较强

3. 总结

  1. 在实际开发中,建议使用StringRedisTemplate进行redis操作,拿到string后在业务中进行对应的转换
  2. 不想在业务中进行转换可以使用GenericToStringSerializer进行序列化操作,但是需要注意class名,路径和成员变量的修改导致的序列化失败问题
  3. ByteArrayRedisSerializer,GenericToStringSerializer,JdkSerializationRedisSerializer 不建议使用