美文网首页
Spring Cache中Redis主流序列化器缺陷及改进

Spring Cache中Redis主流序列化器缺陷及改进

作者: 清蒸三文鱼_ | 来源:发表于2021-03-31 21:25 被阅读0次

背景

FastJson和Jackson的序列化器缺存在缺陷, 不尽如人意, 在结合@Cacheable注解中反序列化可能会失败, 或者在原来的json中夹杂中很多无用的类型信息, 导致json格式的字符串无法通用, 或者解析失败等等;

接下来举例说明一下对应序列化器对应会遇到的问题, 并实现一个自定义的通用对象序列化器. maven和application.yml的配置可参考之前的 文章

示例代码

@RestController
public class Web {
    @Cacheable(value = "cache_list")
    @GetMapping("list")
    public List<SimpleBook> list() {
        ArrayList<SimpleBook> books = new ArrayList<>();
        books.add(new SimpleBook());
        books.add(new SimpleBook());
        return books;
    }
    @Cacheable(value = "cache_get")
    @GetMapping("get")
    public SimpleBook get() {
        return new SimpleBook();
    }
}
public class SimpleBook {
    private String name = "book1";
    private Double price = 123.4D;
  //省略get set
}
Redis缓存管理配置
@EnableCaching
@Configuration
public class RedisCacheConfig extends CachingConfigurerSupport {
    @Autowired
    private RedisConnectionFactory redisConnectionFactory;

    @Override
    @Bean
    public CacheManager cacheManager() {
        RedisSerializer<Object> redisSerializer = new GenericJackson2JsonRedisSerializer();
        //轮流测试
        //redisSerializer = new GenericFastJsonRedisSerializer();
        //redisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        //redisSerializer = new FastJsonRedisSerializer(Object.class);
        RedisCacheConfiguration defaultCacheConfig = RedisCacheConfiguration.defaultCacheConfig().serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer));
        RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory);
        return RedisCacheManager.builder(redisCacheWriter).cacheDefaults(defaultCacheConfig).build();
    }
}

缺陷

FastJsonRedisSerializer和Jackson2JsonRedisSerializer

这两个序列化器在进行非集合的缓存操作, 会报类型转换异常, 即调用上述代码中的get()方法.但在调用list()方法的时候确是正常的, 且是常规的json字符串

  • FastJson报java.lang.ClassCastException: com.alibaba.fastjson.JSONObject cannot be cast to com.cache.demo.SimpleBook

  • Jackson报java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to com.cache.demo.SimpleBook

FastJson类型转换失败
JackSon类型转换失败
json格式
GenericFastJsonRedisSerializer和GenericJackson2JsonRedisSerializer
  • GenericJackson2JsonRedisSerializer序列化后的数据携带了类型的信息@class, 同时为非json格式字符串; 当json的工具不一样时会导致解析失败

    GenericFastJsonRedisSerializer格式
  • GenericFastJsonRedisSerializer和上面的类似, 不同的时序列化后的数据是json格式的, 但是对于Double和Float类型的, 会携带D和F这些标识, 同样导致无法通用


    GenericFastJsonRedisSerializer格式

改进

综合上述的问题, 究其原因是没有对@Cacheable方法上的类型进行绑定, 所以改进的思路是自定义一个序列化器, 扫描注解上的返回类型进行一一对应的解析

自定义的序列化器

  • Jackson
public class JacksonRedisSerializer<T> implements RedisSerializer<T> {
    private final Type type;
    private final ObjectMapper objectMapper = new ObjectMapper();

    public JacksonRedisSerializer(Type type) {
        this.type = type;
    }

    @Override
    public byte[] serialize(T t) throws SerializationException {
        try {
            return objectMapper.writeValueAsBytes(t);
        } catch (JsonProcessingException e) {
            throw new SerializationException("serialize fail", e);
        }
    }

    @Override
    public T deserialize(byte[] bytes) throws SerializationException {
        try {
            JavaType javaType = objectMapper.constructType(type);
            return objectMapper.readValue(bytes, javaType);
        } catch (Exception e) {
            throw new SerializationException("deserialize by type fail", e);
        }
    }
}
  • FastJson
public class FastJsonRedisSerializer<T> implements RedisSerializer<T> {
    private final Type type;

    public FastJsonRedisSerializer(Type type) {
        this.type = type;
    }

    @Override
    public byte[] serialize(T t) throws SerializationException {
        return JSON.toJSONBytes(t);
    }

    @Override
    public T deserialize(byte[] bytes) throws SerializationException {
        return JSON.parseObject(bytes,type);
    }
}

配置

@EnableCaching
@Configuration
public class RedisCacheConfig extends CachingConfigurerSupport {
    @Autowired
    private RedisConnectionFactory redisConnectionFactory;
    @Autowired
    private ApplicationContext applicationContext;

    @Override
    @Bean
    public CacheManager cacheManager() {
        RedisCacheConfiguration defaultCacheConfig = RedisCacheConfiguration.defaultCacheConfig()
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new JacksonRedisSerializer<>(Object.class)));
        return RedisCacheManager.builder(RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory))
                .cacheDefaults(defaultCacheConfig)
                .withInitialCacheConfigurations(buildInitCaches())
                .build();
    }

    private Map<String, RedisCacheConfiguration> buildInitCaches() {
        HashMap<String, RedisCacheConfiguration> cacheConfigMap = new HashMap<>();
        Arrays.stream(applicationContext.getBeanNamesForType(Object.class))
                .map(applicationContext::getType).filter(Objects::nonNull)
                .forEach(clazz -> {
                            ReflectionUtils.doWithMethods(clazz, method -> {
                                ReflectionUtils.makeAccessible(method);
                                Cacheable cacheable = AnnotationUtils.findAnnotation(method, Cacheable.class);
                                if (Objects.nonNull(cacheable)) {
                                    for (String cache : cacheable.cacheNames()) {
                                        RedisSerializationContext.SerializationPair<Object> sp = RedisSerializationContext.SerializationPair
                                                .fromSerializer(new JacksonRedisSerializer<>(method.getGenericReturnType()));
                                        cacheConfigMap.put(cache, RedisCacheConfiguration.defaultCacheConfig().serializeValuesWith(sp));
                                    }
                                }
                            });
                        }
                );
        return cacheConfigMap;
    }
}

重复运行接口不报错, 且显示格式正常则测试通过

相关文章

网友评论

      本文标题:Spring Cache中Redis主流序列化器缺陷及改进

      本文链接:https://www.haomeiwen.com/subject/jveahltx.html