【小家Spring】Redis序列化、RedisTemplate序列化方式大解读,介绍Genericjackson2jsonredisserializer序列化器的坑(中)

简介: 【小家Spring】Redis序列化、RedisTemplate序列化方式大解读,介绍Genericjackson2jsonredisserializer序列化器的坑(中)

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信息的:


image.png



========> 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接,就直接抛出转换异常了


image.png


从上图中我们可以清晰的看见,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);
    }


image.png


我们发现,里面装的竟然,竟然是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);


image.png


为何我的泛型类型丢失了呢?向上追溯一步,我们发现:


  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进行处理。因此出现此种现象也是在情理之中的。

相关文章
|
9月前
|
NoSQL 安全 Java
深入理解 RedisConnectionFactory:Spring Data Redis 的核心组件
在 Spring Data Redis 中,`RedisConnectionFactory` 是核心组件,负责创建和管理与 Redis 的连接。它支持单机、集群及哨兵等多种模式,为上层组件(如 `RedisTemplate`)提供连接抽象。Spring 提供了 Lettuce 和 Jedis 两种主要实现,其中 Lettuce 因其线程安全和高性能特性被广泛推荐。通过手动配置或 Spring Boot 自动化配置,开发者可轻松集成 Redis,提升应用性能与扩展性。本文深入解析其作用、实现方式及常见问题解决方法,助你高效使用 Redis。
1015 4
|
4月前
|
NoSQL Java 调度
分布式锁与分布式锁使用 Redis 和 Spring Boot 进行调度锁(不带 ShedLock)
分布式锁是分布式系统中用于同步多节点访问共享资源的机制,防止并发操作带来的冲突。本文介绍了基于Spring Boot和Redis实现分布式锁的技术方案,涵盖锁的获取与释放、Redis配置、服务调度及多实例运行等内容,通过Docker Compose搭建环境,验证了锁的有效性与互斥特性。
372 0
分布式锁与分布式锁使用 Redis 和 Spring Boot 进行调度锁(不带 ShedLock)
|
6月前
|
NoSQL Java Redis
Redis基本数据类型及Spring Data Redis应用
Redis 是开源高性能键值对数据库,支持 String、Hash、List、Set、Sorted Set 等数据结构,适用于缓存、消息队列、排行榜等场景。具备高性能、原子操作及丰富功能,是分布式系统核心组件。
638 2
|
8月前
|
消息中间件 缓存 NoSQL
基于Spring Data Redis与RabbitMQ实现字符串缓存和计数功能(数据同步)
总的来说,借助Spring Data Redis和RabbitMQ,我们可以轻松实现字符串缓存和计数的功能。而关键的部分不过是一些"厨房的套路",一旦你掌握了这些套路,那么你就像厨师一样可以准备出一道道饕餮美食了。通过这种方式促进数据处理效率无疑将大大提高我们的生产力。
295 32
|
3月前
|
JSON 网络协议 安全
【Java】(10)进程与线程的关系、Tread类;讲解基本线程安全、网络编程内容;JSON序列化与反序列化
几乎所有的操作系统都支持进程的概念,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。独立性动态性并发性。
244 1
|
3月前
|
JSON 网络协议 安全
【Java基础】(1)进程与线程的关系、Tread类;讲解基本线程安全、网络编程内容;JSON序列化与反序列化
几乎所有的操作系统都支持进程的概念,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。独立性动态性并发性。
260 1
|
7月前
|
存储 Java 编译器
说一说关于序列化/反序列化中的细节问题
我是小假 期待与你的下一次相遇 ~
145 1
|
7月前
|
JSON Java 数据库连接
|
8月前
|
存储 安全 IDE
说一说序列化与反序列化中存在的问题
本文详细解析了Java中的序列化机制,包括序列化的概念、实现方式及应用场景。通过Student类的实例演示了对象的序列化与反序列化过程,并分析了`Serializable`接口的作用以及`serialVersionUID`的重要意义。此外,文章还探讨了如何通过自定义`readObject()`方法增强序列化的安全性,以及解决可序列化单例模式中可能产生的多实例问题。最后提供了代码示例和运行结果,帮助读者深入理解序列化的原理与实践技巧。
224 2
|
8月前
|
JSON JavaScript 前端开发
Go语言JSON 序列化与反序列化 -《Go语言实战指南》
本文介绍了 Go 语言中使用 `encoding/json` 包实现 JSON 与数据结构之间的转换。内容涵盖序列化(`Marshal`)和反序列化(`Unmarshal`),包括基本示例、结构体字段标签的使用、控制字段行为的标签(如 `omitempty` 和 `-`)、处理 `map` 和切片、嵌套结构体序列化、反序列化未知结构(使用 `map[string]interface{}`)以及 JSON 数组的解析。最后通过表格总结了序列化与反序列化的方法及类型要求,帮助开发者快速掌握 JSON 数据处理技巧。