Lombok首字母小写,第二个字母大写,jackson反序列化失败

记一次接口调用字段映射失败问题排查

在写接口的时候遇到一个很神奇的问题,编写一个post接口,在使用包装类接收body的时候发现有个字段映射不上。代码如下

@RestController
public class TestController {
    @PostMapping("test")
    public TestDto test(@RequestBody TestDto testDto) {
        return testDto;
    }
}
..........
@Getter
@Setter
public class TestDto {
    private String sName;
    private String value;
}

TestDto中的value可以正常获取值,但是sName却没值。
根据我多年的开发经验,推测应该是字段名的问题,大家都知道springboot接口反序列化是用的jackson,而jackson又是调用的getter和setter实现的序列化和反序列化,这样咱们大概就有一个方向。直接看一下class文件,看看@getter和@setter注解生成的方法长什么样

看起来感觉没什么问题。
那就只能debug看了,为了方便调试,手动编写getter和setter方法(复制过来),然后在setSName方法上打上断点,结果根本没停,说明根本就没有调用该方法。那我们再试试在setValue方法上打上断点,这次走到了

看一下方法调用,发现前几个都是invoke,没啥用,往前看几个,发现deserializeAndSet方法,没看到什么有用的信息

再往前看一个,看看deserializeFromObject方法里面,发现这么一个判断

看一下_beanProperties

发现这里就是保存类字段的信息地方,但是,嗯?为什么是sname,难怪我们的sName无法映射上。
那我们接下来就找找看,这个_beanProperties到底是怎么来的

在_beanProperties上打上断点,调一下接口,嗯?怎么没停?难不成这个是系统启动的时候就加载好的,重启一下试试。还是没有!再调一下接口试试,这次终于是停了

我们再来看看这properties是哪来的

继续深入

终于看到了我们熟悉的内容,这不就是我们要找的东西吗,发现又是从props来的,再回头

继续在_properties上打上断点,注意一定要把Field access勾选上,不然没办法监听到参数的使用

重启服务,调用接口,然后进入addProperty方法

然后继续反推,发现propDefs

阅读方法后发现数据来源于beanDesc.findProperties(),深入得到

继续在这个新的_properties上打上断点,重启服务,跳过几个无用断点,调试接口

深入方法,发现collectAll方法

看到_addFields和_addMethods方法,感觉终于要接近真相了!
接下来我们就看看collectAll方法到底做了什么

可以看到,在_addFields之后,props里面已经存放了TestDto的两个字段,这个时候sName的key还是对的

进入_addMethods方法,这段代码的逻辑是遍历类里的所有方法,如果方法的入参是0个就尝试从add getter方法,如果方法入参是1个则尝试add setter方法。
进入_addGetterMethod方法,发现如下逻辑,如果方法上没有添加json注解就会尝试从方法名称中解析字段名

进入findNameForRegularGetter方法,发现最终是调用的legacyManglePropertyName解析字段名

进入该方法,终于真相大白,重点看下面这段逻辑

这个方法中和方法名一起传进来的还有getter字符的offset用于去除get前缀,然后得到方法中的属性名,首先将属性名的第一个字符变为小写,如果本来就是小写的话直接返回属性名,如getvalue->value。
如果不是则继续遍历剩余字符,将每一个遇到的大写字符都转化为小写,直到遇到第一个小写的字符,然后返回属性名。如getVAlue->value,
getSName->sname,getURL->url。本来_addMethods方法是为了给每个field添加对应的getter和setter方法的,但是现在由于从getter方法中解析的名称和真实的field不一致,就导致会新增一个该名称的字段,我们的sname就是这么来的

这个时候props里面实际上有三个字段,而sName里面是没有getter,setter方法的,我们的getSName方法被sname字段拿去了。然后在经过_removeUnWantedProperties(props)方法之后,没有getter和setter方法的字段就被移除了!

结论

jackson反序列化的逻辑是,先找到类的成员变量field,然后从getter setter方法中反推属性名,为field和方法添加映射,而jackson反推属性名的逻辑是方法中去掉get的部分后跟着的连续大写字符都转换为小写字符。

写在最后

实际上这也和lombok生成getter方法的逻辑有关,如果我用idea自动生成getter方法的话,这个逻辑和jackson是一致的

posted @ 2023-03-30 15:25  轩与木修  阅读(607)  评论(0编辑  收藏  举报