• 一个反序列化问题引发的思考


    项目场景:

    两个团队合作,A团队和B团队合作,A团队通过kafka发送了一段消息是JSON字符串,B团队接收到这段JSON字符串之后反序列化后,然后进行下一步的数据处理


    问题描述

    B团队发送过来的JSON字符串,经过反序列化后,发现某个字段为空,但是消息体里是有该数据的。
    此处为模拟场景,数据均是示例

    @Data
    static class MyTest {
        private Long updateTime;
        private Long uTime;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    A团队发送过来的JSON消息是

    {"updateTime":1666852055159,"uTime":1666852055159}
    
    • 1

    我们把这个字符串反序列化成MyTest对象之后,再序列化之后,得到了

    {"updateTime":1666852055159}
    
    • 1

    debug一下代码,可以发现,uTime字段没有设置进去
    在这里插入图片描述

    原因分析:

    因为参与过A团队的code review,印象中对方用的是Gson进行对象的JSON序列化,然后发送到MQ;而我们接收到消息之后是使用Jackson进行JSON反序列化。所以第一反应,就是两边的JSON序列化和反序列化使用组件不同导致。
    于是我们针对同一个对象,分别进行Gson的JSON序列化和Jackson的序列化

    public static void main(String[] args) {
            long time = System.currentTimeMillis();
            MyTest myTest = new MyTest();
            myTest.setUTime(time);
            myTest.setUpdateTime(time);
            Gson gson = new Gson();
            String json = gson.toJson(myTest);
            System.out.println("gson:" + json);
            System.out.println("jackson:" + JsonTool.writeToString(myTest));
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    输出结果如下:

    gson:{"updateTime":1666854574241,"uTime":1666854574241}
    jackson:{"updateTime":1666854574241,"utime":1666854574241}
    
    • 1
    • 2

    可以发现,Gson将uTime序列化还是uTime,但是Jackson将uTime序列化成utime了,所以在反序列化的时候,Jackson无法将JSON字符串里的uTime的值绑定到uTime字段上。

    后来通过查看源码发现,默认情况下,Jackson生成JSON的key的逻辑

    1. 把所有field找到,作为key
    2. 把所有set方法后面的字符串基于规则X生成key
    3. 把所有get方法后面的字符串基于规则X生成key
    4. 所有没有set和get方法的key都remove掉
    
    • 1
    • 2
    • 3
    • 4

    规则X的逻辑:

    第一个字母强制小写,从第二个字母开始找小写字母,如果是大写,就改为小写,如果是小写就把剩余的字母拼接到已有的部分key上。
    譬如setUTime方法,去掉set只剩下UTime,U强制小写,有了u;继续找下一个字母,T,不是小写,改为小写,有了ut,继续找下一个字母i,是小写,就把剩余的 ime 一起拼接到已有的 ut 后面,于是有了utime。
    
    • 1
    • 2

    而原本字段uTime,推测不到set方法和get方法,导致被remove掉了,setUTime和getUTime和新生成的key,“utime”绑定了。
    规则X的逻辑见代码 BeanUtil#legacyManglePropertyName

    protected static String legacyManglePropertyName(final String basename, final int offset)
    {
      final int end = basename.length();
      if (end == offset) { // empty name, nope
        return null;
      }
      // next check: is the first character upper case? If not, return as is
      char c = basename.charAt(offset);
      char d = Character.toLowerCase(c);
    
      if (c == d) {
        return basename.substring(offset);
      }
      // otherwise, lower case initial chars. Common case first, just one char
      StringBuilder sb = new StringBuilder(end - offset);
      sb.append(d);
      int i = offset+1;
      // 注意此处逻辑,从第二个字母开始遍历,找到第一个小写字母,将其整体拼接;如果是大写字母,就拼接其小写字母
      // 譬如 setUTime,从 T 开始,由于T不是小写字母,如果拼接t,后面的i是小写字母,就把i到结尾整体拼接,得到time
      for (; i < end; ++i) {
        c = basename.charAt(i);
        d = Character.toLowerCase(c);
        if (c == d) {
          sb.append(basename, i, end);
          break;
        }
        sb.append(d);
      }
      return sb.toString();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    而Gson默认的就是使用field的key。


    解决方案:

    以下三种方法都可以,具体场景具体选择即可

    1. 不复用,或者重写A团队提供的jar包,在uTime字段上增加注解@JsonProperty
    2. A团队和B团队使用同一种JSON序列化组件
    3. A团队在提供的jar里包含自己的序列化组件
  • 相关阅读:
    陕西省助理评审申报,看这文章就够了
    结合pyttsx3与pynput模拟实现自动发送qq消息
    centos8同步时间安装时间校准服务
    API_String的重要特性
    .NET源码解读kestrel服务器及创建HttpContext对象流程
    elasticsearch快照生成与恢复
    WPF 控件专题 GroupBox控件详解
    ABeam Insight | 智能制造系列(4):物联网(IoT)× 智能制造
    Mysql数据库基础
    SM2签名算法中随机数K的随机性对算法安全的影响
  • 原文地址:https://blog.csdn.net/liangsheng_g/article/details/127551619