Spring Cache中Redis主流序列化器缺陷及改進(jìn)

背景

FastJson和Jackson的序列化器缺存在缺陷, 不盡如人意, 在結(jié)合@Cacheable注解中反序列化可能會(huì)失敗, 或者在原來(lái)的json中夾雜中很多無(wú)用的類(lèi)型信息, 導(dǎo)致json格式的字符串無(wú)法通用, 或者解析失敗等等;

接下來(lái)舉例說(shuō)明一下對(duì)應(yīng)序列化器對(duì)應(yīng)會(huì)遇到的問(wèn)題, 并實(shí)現(xiàn)一個(gè)自定義的通用對(duì)象序列化器. 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();
        //輪流測(cè)試
        //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

這兩個(gè)序列化器在進(jìn)行非集合的緩存操作, 會(huì)報(bào)類(lèi)型轉(zhuǎn)換異常, 即調(diào)用上述代碼中的get()方法.但在調(diào)用list()方法的時(shí)候確是正常的, 且是常規(guī)的json字符串

  • FastJson報(bào)java.lang.ClassCastException: com.alibaba.fastjson.JSONObject cannot be cast to com.cache.demo.SimpleBook

  • Jackson報(bào)java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to com.cache.demo.SimpleBook

FastJson類(lèi)型轉(zhuǎn)換失敗

JackSon類(lèi)型轉(zhuǎn)換失敗

json格式
GenericFastJsonRedisSerializer和GenericJackson2JsonRedisSerializer
  • GenericJackson2JsonRedisSerializer序列化后的數(shù)據(jù)攜帶了類(lèi)型的信息@class, 同時(shí)為非json格式字符串; 當(dāng)json的工具不一樣時(shí)會(huì)導(dǎo)致解析失敗

    GenericFastJsonRedisSerializer格式

  • GenericFastJsonRedisSerializer和上面的類(lèi)似, 不同的時(shí)序列化后的數(shù)據(jù)是json格式的, 但是對(duì)于Double和Float類(lèi)型的, 會(huì)攜帶D和F這些標(biāo)識(shí), 同樣導(dǎo)致無(wú)法通用


    GenericFastJsonRedisSerializer格式

改進(jìn)

綜合上述的問(wèn)題, 究其原因是沒(méi)有對(duì)@Cacheable方法上的類(lèi)型進(jìn)行綁定, 所以改進(jìn)的思路是自定義一個(gè)序列化器, 掃描注解上的返回類(lèi)型進(jìn)行一一對(duì)應(yīng)的解析

自定義的序列化器

  • 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;
    }
}

重復(fù)運(yùn)行接口不報(bào)錯(cuò), 且顯示格式正常則測(cè)試通過(guò)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容