Redis缓存序列化问题

目录

  • RedisSerializer
    • RedisConfig
      • KeyGenerator
      • ValueSerializer
    • SimpleGrantedAuthority

RedisSerializer

RedisTemplate默认使用的是JdkSerializationRedisSerializer,StringRedisTemplate默认使用的是StringRedisSerializer

SpringBoot提供的Redis存储序列化方式,常用的有以下几种:

  • JdkSerializationRedisSerializer:将数据序列化为对象;
  • StringRedisSerializer:将数据序列化为字符串;
  • Jackson2JsonRedisSerializer、GenericJackson2JsonRedisSerializer:将数据序列化为json;

JdkSerializationRedisSerializer: 使用JDK提供的序列化功能。
优点是反序列化时不需要提供类型信息(class);
缺点是需要实现Serializable接口,还有序列化后的结果非常庞大,是JSON格式的5倍左右,这样就会消耗redis服务器的大量内存。

Jackson2JsonRedisSerializer: 使用Jackson库将对象序列化为JSON字符串。
优点是速度快,序列化后的字符串短小精悍,不需要实现Serializable接口。缺点也非常致命,那就是此类的构造函数中有一个类型参数,必须提供要序列化对象的类型信息(.class对象)。

RedisConfig

KeyGenerator

默认使用SimpleKeyGenerator

public class SimpleKeyGenerator implements KeyGenerator {
  //...
    public Object generate(Object target, Method method, Object... params) {
        return generateKey(params);
    }
    public static Object generateKey(Object... params) {
        if (params.length == 0) {
            return SimpleKey.EMPTY;
        } else {
        //...
           return new SimpleKey(params);
        }
    }
}
public class SimpleKey implements Serializable {
    public static final SimpleKey EMPTY = new SimpleKey(new Object[0]);
    private final Object[] params;
    private transient int hashCode;
    //...
    public String toString() {
        return this.getClass().getSimpleName() + " [" + StringUtils.arrayToCommaDelimitedString(this.params) + "]";
    }
}

从上述代码可见,会将参数params通过SimpleKey组装,生成一长串字符串(类名+[params…])
一般可覆写如下:

import org.apache.commons.codec.digest.DigestUtils;

 @Bean
 @Override
 public KeyGenerator keyGenerator() {
        return (target, method, params) -> {
            Map<String, Object> container = new HashMap<>(4);
            Class<?> targetClassClass = target.getClass();
            // 类地址
            container.put("class", targetClassClass.toGenericString());
            // 方法名称
            container.put("methodName", method.getName());
            // 包名称
            container.put("package", targetClassClass.getPackage());
            // 参数列表
            for (int i = 0; i < params.length; i++) {
                container.put(String.valueOf(i), params[i]);
            }
            // 转为JSON字符串
            String jsonString = ObjectMapperUtil.obj2String(container);
            // 做SHA256 Hash计算,得到一个SHA256摘要作为Key
            return DigestUtils.sha256Hex(jsonString);
        };
    }

ValueSerializer

问题:
从redis取数据将json反序列化为具体pojo时,报错java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to com.XXX
解决:
在ObjectMapper添加一行
objectMapper.activateDefaultTyping(objectMapper.getPolymorphicTypeValidator(), ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);

原先使用的方法enableDefaultTyping已经过期,有安全漏洞Jackson-databind。

    @SuppressWarnings("all")
    @Bean(name = "redisTemplate")
    @ConditionalOnMissingBean(name = "redisTemplate")
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<Object, Object> template = new RedisTemplate<>();
        // 使用Jackson2JsonRedisSerialize替换默认序列化方式
        Jackson2JsonRedisSerializer<?> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        jackson2JsonRedisSerializer.setObjectMapper(ObjectMapperUtil.objectMapper);
        template.setValueSerializer(jackson2JsonRedisSerializer);
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        // key的序列化采用StringRedisSerializer
        template.setKeySerializer(new StringRedisSerializer());
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }

SimpleGrantedAuthority

问题:
Cannot construct instance of 'org.springframework.security.core. authority.SimpleGrantedAuthority'
解决:
在ObjectMapper 添加一行objectMapper.addMixIn(SimpleGrantedAuthority.class,SimpleGrantedAuthorityMixin.class);

详细请看:
实体类字段为接口的json序列化报错的解决方法 以 SpringSecurity UserDetails实现类 GrantedAuthority 为例

你可能感兴趣的:(code,redis,缓存,spring,boot)