背景
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



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;
}
}
重复运行接口不报错, 且显示格式正常则测试通过
网友评论