这个字段我明明传了呀,为什么收不到 - Spring 中首字母小写,第二个字母大写造成的参数问题

这篇具有很好参考价值的文章主要介绍了这个字段我明明传了呀,为什么收不到 - Spring 中首字母小写,第二个字母大写造成的参数问题。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

问题现象

PS:本文首发于微信公众号:技术角落。感兴趣的同学可以查看并关注:https://mp.weixin.qq.com/s/yL1HpgTQdIPVjuFsq2YGFg

vSwitchIduShapeiPhone... 这类字段名,有什么特点?很容易看出来吧,首字母小写,第二个字母大写。它们看起来确实是符合 Java 中对字段所推崇的“小驼峰命名法”,即第一个单词小写,后面的单词首字母大写。但是,如果你在项目中给 POJO 类的字段以这种形式进行命名的话,那么可能会碰到 序列化/反序列化 的问题。。。下面就是一个我在项目中亲自踩过的坑

Spring Web 开发中,我们往往使用 POJO 对象来充当请求传递时的 body。例如现有一个用于传输的 POJO 对象,我将其进行简化后如下

@Data
public class InstanceRequest {
	private String vSwitchId;
}

然后在 Controller 中使用这个对象作为 @RequestBody 来获得请求体,并在处理逻辑中输出 vSwitchId字段

@RestController
public class InstanceController {
    @RequestMapping("/createInstance")
	public String createInstance(@RequestBody InstanceRequest request) {
        // do something
        System.out.println(request.getVSwitchId());
        return "success";
}

运行上述应用后,我信心满满的发送一个 HTTP 请求进行测试,充满信心地认为控制台里会打印我传过去的信息

POST /createInstance HTTP/1.1
Content-Type: application/json

{
	"vSwitchId": "xxxx"
}

结果却发现,控制台输出了一个大大的 null。。一脸懵逼,我逐字对比自己发送的 JSON 字段名和类里面的字段名。。v...S...w...i...t...c...h...I...d... 没问题呀,一个字母都不差呀,为什么收不到呢?

vSwitchId字段为什么没有成功解析到?我们知道 Spring 是通过 jackson 框架来进行序列化和反序列化的,因此需要深入 jackson 的源码,看看为什么这个字段没有被成功反序列化。

深入 Jackson 源码探究原因

Jackon 中,主要通过AbstractJackson2HttpMessageConverter.readJavaType方法将 HTTP 请求中的消息体转换为对象,因此直接对其打断点进行调试

这个字段我明明传了呀,为什么收不到 - Spring 中首字母小写,第二个字母大写造成的参数问题

根据断点逐步推进,进入 ObjectMapper._readMapAndClose方法

这个字段我明明传了呀,为什么收不到 - Spring 中首字母小写,第二个字母大写造成的参数问题

看到这里有 _findRootDeserializer方法,顾名思义,应该是根据当前想要转换的对象类型,来寻找对应的反序列化器了。那么继续进去看看...

往下层层递进后,找到创建反序列化器的地方,在 DeserializerCache._createDeserializer里,也就是说是在 DeseializerCache 里面执行创建的步骤,这其实是很常见的 缓存+懒加载 模式:要使用的时候,首先去缓存里面拿,拿不到的时候再创建,创建完直接加入缓存。

这个字段我明明传了呀,为什么收不到 - Spring 中首字母小写,第二个字母大写造成的参数问题

在创建反序列化器的方法里,有个 BeanDescription类值得注意,它指的是类的描述,因此猜测在这个类里面,我们的 POJO 类的字段应该已经被分析完毕了,那么上面的 vSwitchId 到底被分析成了啥,也可以在里面看到。

这个字段我明明传了呀,为什么收不到 - Spring 中首字母小写,第二个字母大写造成的参数问题

该类里面有 POJOPropertiesCollector ,那么我们 POJO 类的字段应该是被收集在这个类里面了。

值得注意的是,这是一个懒加载的类,内部的分析逻辑只有在第一次被用到时才会执行。分析逻辑在 POJOPropertiesCollector.collecAll()这个方法里面。

下面重点就来了,看看这个方法

这个字段我明明传了呀,为什么收不到 - Spring 中首字母小写,第二个字母大写造成的参数问题

方法主要逻辑如下:

  • 首先初始化了 props,存储所有反序列化过程中需要的属性
  • 通过_addFields(props)方法从类的字段中抽取属性并加入 props 中
  • 通过_addMethods(props)方法从类的 getter 和 setter 字段中抽取属性并加入 props 中
  • 通过 _removeUnwantedProperties(props)方法从 props 中剔除掉不想要的属性。哪些属性会被剔除?从代码可以看出,字段、getter、setter 都是私有属性、或者已经被标记为 ignore 的属性,是需要被剔除的。

通过调试发现,执行完 _addFields 后,vSwitchId字段成功加入

这个字段我明明传了呀,为什么收不到 - Spring 中首字母小写,第二个字母大写造成的参数问题

再执行完 _addMethods(props)后,神奇的事情发生了,加入了另外一个 props vswitchId

这个字段我明明传了呀,为什么收不到 - Spring 中首字母小写,第二个字母大写造成的参数问题

接下来,执行 _removeUnwantedProperties(props)之后这个字段我明明传了呀,为什么收不到 - Spring 中首字母小写,第二个字母大写造成的参数问题

发现 vSwitchId这个正确的属性已经被剔除了,反而留下了 vswitchId这个有问题的属性。这是为什么呢?上面提到,_removeUnwantedProperties会剔除私有的属性,vSwitchId这个 props 是来自字段的,而字段本身是私有的,因此它被剔除了。

接下来我们需要搞清楚为什么从 getter、setter 中拿到的属性是 vswitchId而不是 vSwitchId

首先,getter 和 setter 是哪里来的?项目中我使用的 Lombok,也就是说 getter 和 setter 是由 Lombok 生成的。在大多数 IDE 中,如果使用 Lombok 生成 setter 方法,它将会被自动隐藏并不会显示在源代码中。如果想要查看生成的方法名称,通常 IDE 会提供一个叫做“Structure”(结构)或“Outline”(大纲)的功能,它可以列出类的所有成员变量和方法,其中也包括由 Lombok 生成的 setter 方法。

这个字段我明明传了呀,为什么收不到 - Spring 中首字母小写,第二个字母大写造成的参数问题

可以看到 get 和 set 方法的名称分别是 getVSwitchIdsetVSwitchId。接下来看看 Jackson 具体是如何解析方法,从而得到 props 的。相关代码在 DefaultAccessorNamingStrategy.legacyManglePropertyName

这个字段我明明传了呀,为什么收不到 - Spring 中首字母小写,第二个字母大写造成的参数问题

以上处理流程用大白话解释一下:首先会根据 offset字段去除前面的三个字母,一般为 get 或 set。去除前面三个字母 'set' 后,发现第一个字母是大写的,因此将第一个字母小写,然后接着往后找,如果后面的还是大写,接着变小写...直到找到了一个本来就是小写的字母后,才将后面所有的字母一股脑添加进来。由于 setVSwitchId在去除前面的 set 后,前面两个字母都是大写,因此在这种处理逻辑下,最后得到的属性名为 vswitchId。换句话说,如果 set 方法的名称是 setvSwitchId,那么处理后得到的就是正确的 vSwitchId

说到这里,问题其实就明了了,这个其实是由于 Lombok 生成 getter、setter 方法的语义规范与 Jackson 处理 get set 方法之间的不一致性,导致的属性名无法匹配上的问题。

Lombok

其实在 Lombok 社区里,也有人提出过这个问题,详见 https://github.com/projectlombok/lombok/issues/2693。

可以看出,这个其实是规范的问题,目前没有一个定论。。Lombok 认为自己生成 set、get 方法的规范没有问题,Jackson 那边也认为自己根据 set、get 方法来解析字段名的规范也没有问题,公说公有理,婆说婆有理。。不过,不管是谁有理,最后受到伤害的是我们开发者呀,只要你的项目中同时用到了 Lombok 和 Jackson,就会遇到这个问题。对于没有接触过这个问题的开发者来说,这个问题其实是会平白无故浪费很多时间的。

不过,Lombok 社区还是提出了一个 PR 来解决这个问题,详见 https://github.com/projectlombok/lombok/pull/2996 。

在以上 PR 中,Lombok 社区提供了一个配置项,

lombok.accessors.capitalization = [basic | beanspec] (default: basic)

默认为 basic,也就是 Lombok 默认的行为,会生成 setVSwitchId这种方法名。

如果将其修改为 beanspec,那么会保持与 Spring、Jackson 相同的规范, 此时会生成 setvSwitchId这种方法名。

详情也可以看 Lombok 的官方文档 https://projectlombok.org/features/GetterSetter

这个字段我明明传了呀,为什么收不到 - Spring 中首字母小写,第二个字母大写造成的参数问题

其中最后一句话很有意思,“Both strategies are commonly used in the java ecosystem, though beanspec is more common“。这意思是,“我承认 Jackson 那边使用的规范更常用一些,但是我默认还是要坚持我的规范...”。

解决方案

讲到这里,解决方案其实就出来了。这里介绍三种解决方案吧

方案一

使用 Lombok 的配置来解决。在项目根目录下创建 lombok.config文件,并添加以下配置项即可

lombok.accessors.capitalization = beanspec

方案二

利用 IDE、或者手动生成 getter、setter 方法

public String getvSwitchId() {
    return vSwitchId;
}

public void setvSwitchId(String vSwitchId) {
    this.vSwitchId = vSwitchId;
}

方案三

利用 Jackson 的 JsonProperty 注解强行指定属性名

@Data
public class InstanceRequest {
    @JsonProperty(value = "vSwitchId")
	private String vSwitchId;
}

总结

我自己从这个事件中总结出来了一点经验。在 Java 里面,给类属性取名的时候,以前我想着是只要满足小驼峰命名法就万事大吉,不会有什么问题了。。。现在我知道了,并不是说满足小驼峰就万事大吉了,如果碰到 首字母小写、第二个字母大写 的这种情况,还是要特别注意,尤其是当这个类还被用于序列化/反序列化时,一定要注意其处理的规范性,要写(生成)生成符合 Java Bean 规范的 set、get 方法,否则这个小小的字段在反序列化时会一直困扰着你。。让你一直抓狂 “这个字段我明明传了呀,为什么 Spring 就是收不到”。文章来源地址https://www.toymoban.com/news/detail-438896.html

到了这里,关于这个字段我明明传了呀,为什么收不到 - Spring 中首字母小写,第二个字母大写造成的参数问题的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • <dependency> idea中为什么这个变黄色

      在IDE中,当你的代码出现黄色高亮时,通常表示存在警告或建议的提示。对于Maven的 dependency 标签来说,黄色高亮可能有以下几种原因: 依赖项未找到:黄色高亮可能表示IDE无法找到指定的依赖项。这可能是由于配置错误、网络问题或仓库中缺少该依赖项等原因导致的。你

    2024年02月14日
    浏览(40)
  • Pycharm这个更新索引是个什么操作,为什么每次启动,都会进行?

    点击上方“ Python爬虫与数据挖掘 ”,进行关注 回复“ 书籍 ”即可获赠Python从入门到进阶共10本电子书 今 日 鸡 汤 九重城阙烟尘生,千乘万骑西南行。 大家好,我是皮皮。 一、前言 前几天在Python最强王者交流群【吴超建】问了一个 Pycharm 操作的问题,这里拿出来给大家分

    2024年02月01日
    浏览(55)
  • 为什么都说“一入Java深似海”?怎么打破这个魔咒

    在当今数字化时代,编程已经成为一项至关重要的技能。而在众多编程语言中,Java以其广泛的应用领域和强大的功能特性,吸引了无数开发者的目光。无论是Web开发、移动应用还是大数据处理,Java都发挥着举足轻重的作用。然而,正是由于其广泛的应用和深入的特性,Java也

    2024年04月10日
    浏览(50)
  • 为什么需要在bean上使用@EqualsAndHashCode(callSuper = true)这个注解

    我们还是写一个案例,来探讨它的作用。 写三个类,一个是子类Child.class,一个是父类Person.class,一个是测试类Test.class Person父类很简单,就一个属性code; Child子类也很简单,继承了父类之后,自己也是定义了一个属性name; Test类: 创建了两个Child,属性code是不一样的,一个

    2024年02月03日
    浏览(36)
  • 是时候回答【我为什么要学习 Go 语言(golang)】这个问题了

    想必每个人在学习新事物之前,都会扪心自问:“我为什么要学习它呢?” 正如我们读 四大名著 一般,也只有在您读过了 四大名著 后,再细看中国几千年历史不就是 天下大势合久必分,分久必合 ,再者,便是与友数人相聚,席间您述说您通勤时所遇到有意思的事了,而您

    2023年04月09日
    浏览(51)
  • 为什么消费返利模式层出不穷?这个消费返利玩法值得你借鉴

    大家好,我是林工,不知道大家听说过消费返利没有?消费返利是互联网常见的一个商业模式,是指互联网平台将自己所销售的商品和服务让渡给消费者并获得一定比例的报酬,消费者通过平台享受到的佣金和消费总额(金额)之间的差额作为回报,消费者可以在购买商品后

    2024年02月01日
    浏览(45)
  • Python爬虫:为什么你爬取不到网页数据

    前言: 之前小编写了一篇关于爬虫为什么爬取不到数据文章(文章链接为:https://liuze.blog.csdn.net/article/details/105965562),但是当时小编也是胡乱编写的,其实里面有很多问题的,现在小编重新发布一篇关于爬虫爬取不到数据文章,希望各位读者更加了解爬虫。 1. 最基础的爬虫

    2024年02月05日
    浏览(91)
  • 白杨SEO:从董宇辉事件聊聊个人IP这个事,企业为什么要重视个人IP?

    董宇辉事件是指东方甄选因主播董宇辉的小作文到底由谁写的风波,具体详情可以网上搜搜,最近是热搜。下图1产生原因,下图2董宇辉回应截图。 白杨SEO说一下,这里不关注谁对谁错。我想说的是,一是现在个人IP非常重要!二是很多人不清楚,只要是有团队IP网红,都是出

    2024年02月22日
    浏览(57)
  • 为什么区域中找不到无线网络 如何解决区域中找不到无线网络的问题

    区域中找不到无线网络解决办法,无线网朋友这里可以找到答案。 电脑无线上网提示“区域中找不到无线网络”怎么办? 案例分析一: 笔记本电脑之前好好的,突然不能使用无线网络了,提示“区域中找不到无线网络”。主要有如下三个问题: 一、event log被360禁用了,启用

    2024年02月06日
    浏览(69)
  • 小米盒子为什么搜不到电视家?电视安装包解析错误解决方案

    不少的朋友在小米电视盒子上安装了美家市场软件商店后,却发现在市场里面没法安装想要的电视盒子直播软件,这是怎么回事呢?其实大部分原因是电视盒子机制的问题限制了安装,导致部分品牌电视盒子装软件时会弹出“无法安装”的提示。 本身厂商的原因不好解决,但

    2024年02月09日
    浏览(56)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包