Jackson2JsonRedisSerializer和GenericJackson2JsonRedisSerializer的异同
Jackson2JsonRedisSerializer:为我们提供了两个构造方法,一个需要传入序列化对象Class,一个需要传入对象的JavaType:
public Jackson2JsonRedisSerializer(Class<T> type) { this.javaType = getJavaType(type); } public Jackson2JsonRedisSerializer(JavaType javaType) { this.javaType = javaType; }
但因为redisTemplate我们都是单例的,所以这样设置显然是非常不可取的行为。虽然它有好处~~~~~~~~~~
这种序列化方式的好处:他能实现不同的Project之间数据互通(因为没有@class信息,所以只要字段名相同即可),因为其实就是Json的返序列化,只要你指定了类型,就能反序列化成功(因为它和包名无关)
使用这种Json序列化方式果然是可以成功的在不同project中进行序列化和反序列化的。但是,但是,但是:在实际的使用中,我们希望职责单一和高内聚的,所以并不希望我们存在的对象,其它服务可以直接访问,那样就非常不好控制了,因此此种方式也不建议使用~
GenericJackson2JsonRedisSerializer:这种序列化方式不用自己手动指定对象的Class。所以其实我们就可以使用一个全局通用的序列化方式了。使用起来和JdkSerializationRedisSerializer基本一样。
同样的JdkSerializationRedisSerializer不能序列化和反序列化不同包路径对象的毛病它也有。因为它序列化之后的内容,是存储了对象的class信息的:
========> Jackson2JsonRedisSerializer的坑:
存储普通对象的时候没有问题,但是当我们存储带泛型的List的时候,反序化就会报错了:
@Test public void contextLoads() { redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<>(List.class)); ValueOperations<String, List<Person>> valueOperations = redisTemplate.opsForValue(); valueOperations.set("aaa", Arrays.asList(new Person("fsx", 24), new Person("fff", 30))); List<Person> p = valueOperations.get("aaa"); System.out.println(p); //[{name=fsx, age=24}, {name=fff, age=30}] List<Person> aaa = (List<Person>) redisTemplate.opsForValue().get("aaa"); System.out.println(aaa); //[{name=fsx, age=24}, {name=fff, age=30}] }
结论:网上很多帖子都说这样会出问题,但我实验过后发现不会有问题。时间有限,我这个是基于Spring Boot2.1进行测试的,若你们测试的版本有问题,欢迎告知我,我再做进一步的验证,多谢。
========> GenericJackson2JsonRedisSerializer的坑:
@Test public void contextLoads() { redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer()); ValueOperations<String, Long> valueOperations = redisTemplate.opsForValue(); valueOperations.set("aaa", 1L); //Long p = valueOperations.get("aaa"); //转换异常 java.lang.Integer cannot be cast to java.lang.Long Object p = valueOperations.get("aaa"); System.out.println(p); }
**坑1:**泛型里明明返回的就是Long类型,但你用Long接,就直接抛出转换异常了
从上图中我们可以清晰的看见,get出来返回的真实类型竟然是Integer
类型,所以强转肯定报错啊
再看一例:set类型
@Test public void contextLoads() { redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer()); SetOperations<String, Long> setOperations = redisTemplate.opsForSet(); setOperations.add("bbb", 1L); setOperations.add("bbb", 2L); Set<Long> p = setOperations.members("bbb"); System.out.println(p); }
我们发现,里面装的竟然,竟然是Integer类型。这种Java泛型的bug我们在之前的博文里有讲述过,特别坑。这个时候这个变量就是个地雷,只要一碰,就报错
另外,就算你获取的并不是List类型,而是一个值,也必须要转换一下,否则类型转换异常。像下面这么操作才是安全的:
Object teaIdObj = setOperLong.pop(teaCategoryKey); if (teaIdObj != null) { log.info("从redis老师仓库{}里拿到了一个老师{}", teaCategoryKey, teaIdObj); teacherIds.add(Long.parseLong(teaIdObj.toString())); }
类型转换异常原因分析
因为GenericJackson2JsonRedisSerializer这种序列化方式实在是太通用了,所以我还是希望找出原因,解决这个问题的。因此我就跟踪源码,看看到底是哪里出了问题:
执行setOperations.members("bbb")这句最终都到了RedisTemplate的execute方法:
public <T> T execute(RedisCallback<T> action, boolean exposeConnection, boolean pipeline) {...}
方法体的这一行,解析了返回的value值:
T result = action.doInRedis(connToExpose);
tips:Spring Boot1.x此处connToExpose使用的是jedis的,而Boot2.x使用的是Lettuce的了。但是对调用者是透明的,可谓非常友好
继续跟踪发现,最终会调用我们配置好的序列化器进行序列化:
V deserializeValue(byte[] value) { if (valueSerializer() == null) { return (V) value; } return (V) valueSerializer().deserialize(value); }
因此啥都不说了,到GenericJackson2JsonRedisSerializer
去看看它的deserialize
方法吧,就在这一句话:
// 调用了jackson的ObjectMapper方法进行返序列化 但是type为Object.class return mapper.readValue(source, type);
为何我的泛型类型丢失了呢?向上追溯一步,我们发现:
static <T extends Collection<?>> T deserializeValues(@Nullable Collection<byte[]> rawValues, Class<T> type, @Nullable RedisSerializer<?> redisSerializer) { // connection in pipeline/multi mode if (rawValues == null) { return (T) CollectionFactory.createCollection(type, 0); } Collection<Object> values = (List.class.isAssignableFrom(type) ? new ArrayList<>(rawValues.size()) : new LinkedHashSet<>(rawValues.size())); for (byte[] bs : rawValues) { values.add(redisSerializer.deserialize(bs)); } return (T) values; }
我们的类型全部变成了Collection里面的Object类型,我们的泛型就这样丢失了
。所以在序列化的时候,只要遇到数字(或者泛型),自然就是当作Integer来处理了,因此就出现了我们看到的诡异现象。
因为GenericJackson2JsonRedisSerializer本来处理序列化的都是与类型无关的,所以都转换为Object进行处理。因此出现此种现象也是在情理之中的。