这个字段我明明传了呀,为什么收不到 - Spring 中首字母小写,第二个字母大写造成的参数问题
问题现象
vSwitchId
、uShape
、iPhone
... 这类字段名,有什么特点?很容易看出来吧,首字母小写,第二个字母大写。它们看起来确实是符合 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 双色球129期开奖结果
来进行序列化和反序列化的,因此需要深入 jackson 的源码,看看为什么这个字段没有被成功反序列化。
深入 Jackson 源码探究原因
Jackon 中,主要通过AbstractJackson2HttpMessageConverter.readJavaType
方法将 HTTP 请求中的消息体转换为对象,因此直接对其打断点进行调试
根据断点逐步推进,进入 ObjectMapper._readMapAndClose
方法
看到这里有 _findRootDeserializer
方法,顾名思义,应该是根据当前想要转换的对象类型,来寻找对应的反序列化器了。那么继续进去看看...
往下层层递进后,找到创建反序列化器的地方,在 DeserializerCache._createDeserializer
里,也就是说是在 DeseializerCache 里面执行创建的步骤,这其实是很常见的 缓存+懒加载 模式:要使用的时候,首先去缓存里面拿,拿不到的时候再创建,创建完直接加入缓存。
在创建反序列化器的方法里,有个 BeanDescription
类值得注意,它指的是类的描述,因此猜测在这个类里面,我们的 POJO 类的字段应该已经被分析完毕了,那么上面的 vSwitchId 到底被分析成了啥,也可以在里面看到。
该类里面有 POJOPropertiesCollector ,那么我们 POJO 类的字段应该是被收集在这个类里面了。
值得注意的是,这是一个懒加载的类,内部的分析逻辑只有在第一次被用到时才会执行。分析逻辑在 POJOPropertiesCollector.collecAll()
这个方法里面。
下面重点就来了,看看这个方法
方法主要逻辑如下:
- 首先初始化了 props,存储所有反序列化过程中需要的属性
- 通过
_addFields(props)
方法从类的字段中抽取属性并加入 props 中 - 通过
_addMethods(props)
方法从类的 getter 和 setter 字段中抽取属性并加入 props 中 - 通过
_removeUnwantedProperties(props)
方法从 props 中剔除掉不想要的属性。哪些属性会被剔除?从代码可以看出,字段、getter、setter 都是私有属性、或者已经被标记为 ignore 的属性,是需要被剔除的。
通过调试发现,执行完 _addFields
后,vSwitchId
字段成功加入
再执行完 _addMethods(props)
后,神奇的事情发生了,加入了另外一个 props vswitchId
接下来,执行 _removeUnwantedProperties(props)
之后
发现 vSwitchId
这个正确的属性已经被剔除了,反而留下了 vswitchId
这个有问题的属性。这是为什么呢?上面提到,_removeUnwantedProperties
会剔除私有的属性,vSwitchId
这个 props 是来自字段的,而字段本身是私有的,因此它被剔除了。
接下来我们需要搞清楚为什么从 getter、setter 中拿到的属性是 vswitchId
而不是 vSwitchId
。
首先,getter 和 setter 是哪里来的?项目中我使用的 Lombok,也就是说 getter 和 setter 是由 Lombok 生成的。在大多数 IDE 中,如果使用 Lombok 生成 setter 方法,它将会被自动隐藏并不会显示在源代码中。如果想要查看生成的方法名称,通常 IDE 会提供一个叫做“Structure”(结构)或“Outline”(大纲)的功能,它可以列出类的所有成员变量和方法,其中也包括由 Lombok 生成的 setter 方法。
可以看到 get 和 set 方法的名称分别是 getVSwitchId
和 setVSwitchId
。接下来看看 Jackson 具体是如何解析方法,从而得到 props 的。相关代码在 DefaultAccessorNamingStrategy.legacyManglePropertyName
中
以上处理流程用大白话解释一下:首先会根据 offset
字段去除前面的三个字母,一般为 get 或 set。去除前面三个字母 'set' 后,发现第一个字母是大写的,因此将第一个字母小写,然后接着往后找,如果后面的还是大写,接着变小写...直到找到了一个本来就是小写的字母后,才将后面所有的字母一股脑添加进来。由于 setVSwitchId
在去除前面的 set 后,前面两个字母都是大写,因此在这种处理逻辑下,最后得到的属性名为 vswitchId
。换句话说,如果 set 方法的名称是 setvSwitchId
,那么处理后得到的就是正确的 vSwitchId
。
说到这里,问题其实就明了了,这个其实是由于 Lombok 生成 getter、setter 方法的语义规范与 Jackson 处理 get set 方法之间的不一致性,导致的属性名无法匹配上的问题。
Lombok
其实在 Lombok 社区里,也有人提出过这个问题,详见 。
可以看出,这个其实是规范的问题,目前没有一个定论。。Lombok 认为自己生成 set、get 方法的规范没有问题,Jackson 那边也认为自己根据 set、get 方法来解析字段名的规范也没有问题,公说公有理,婆说婆有理。。不过,不管是谁有理,最后受到伤害的是我们开发者呀,只要你的项目中同时用到了 Lombok 和 Jackson,就会遇到这个问题。对于没有接触过这个问题的开发者来说,这个问题其实是会平白无故浪费很多时间的。
不过,Lombok 社区还是提出了一个 PR 来解决这个问题,详见 。
在以上 PR 中,Lombok 社区提供了一个配置项,
lombok.accessors.capitalization = [basic | beanspec] (default: basic)
默认为 basic,也就是 Lombok 默认的行为,会生成 setVSwitchId
这种方法名。
如果将其修改为 beanspec,那么会保持与 Spring、Jackson 相同的规范, 此时会生成 setvSwitchId
这种方法名。
详情也可以看 Lombok 的官方文档
其中最后一句话很有意思,“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 就是收不到”。
这个字段我明明传了呀,为什么收不到 - Spring 中首字母小写,第二个字母大写造成的参数问题的更多相关文章
- Jmeter 从数据库查询多个字段,依次传给登录接口怎么实现?
问题背景: 博文“Jmeter 如何把数据库的数据依次获取作为参数传入下一个请求?附栗子”某天有人留言如下: 看了下当时写的文章,如果从数据库查询多个字段,依次传给登录接口,确实不能合理实现,所以,特 ...
- Spring中servletFileUpload完成上传文件以及文本的处理
JSP: <%@ page language="java" contentType="text/html; charset=UTF-8" pageEnco ...
- Spring中MultipartHttpServletRequest实现文件上传
Spring中MultipartHttpServletRequest实现文件上传 转贴自://my.oschina.net/nyniuch/blog/185266 实现图片上传 用户必须能 ...
- 使用反射创建Bean、Spring中是如何根据类名配置创建Bean实例、Java提供了Class类获取类别的字段和方法,包括构造方法
Java提供了Class类,可以通过编程方式获取类别的字段和方法,包括构造方法 获取Class类实例的方法: 类名.class 实例名.getClass() Class.forNam ...
- C#实体对象序列化成Json并让字段的首字母小写的两种解决方法
引言:最近在工作中遇到与某些API对接的post的数据需要将对象的字段首字母小写.解决办法有两种:第一种:使用对象的字段属性设置JsonProperty来实现(不推荐,因为需要手动的修改每个字段的属性 ...
- js上传文件带参数,并且,返回给前台文件路径,解析上传的xml文件,存储到数据库中
ajaxfileupload.js jQuery.extend({ createUploadIframe: function(id, uri) { //create frame var frameId ...
- ajax传JSON时设置的contenttype导致JAVA中request.getParameter("")怎么也接收不到数据
ajax传JSON时设置的contenttype默认值是application/x-www-form-urlencoded, 当ajax传JSON时设置的contenttype 如果是applicat ...
- 再springMVC中自定义文件上传处理解决与原spring中MultipartResolve冲突问题
相信很多朋友再用springmvc时都遇见了一个问题,那就是自带的获取上传的东西太慢,而且不知道如何修改,其实不然,spring双色球129期开奖结果 既然给我们开放了这个接口,就一定遵从了可扩展性的原则,经过查看org ...
- C#实体对象序列化成Json,并让字段的首字母小写
引言:最近在工作中遇到与某些API对接的post的数据需要将对象的字段首字母小写.解决办法有两种:第一种:使用对象的字段属性设置JsonProperty来实现(不推荐,因为需要手动的修改每个字段的属性 ...
- Spring中为什么不建议使用字段注入
在使用Idea中通过注解注入字段时是否遇见过这样一个提示: Field injection is not recommended(不推荐使用字段注入) 一. 什么是字段注入,Spring中依赖注入的方 ...
随机推荐
- Unity打包ARCore项目失败,但是其他安卓项目成功
//blog.csdn.net/lxbhahaha/article/details/111269980
- WordPress标题分隔符”-“被转义为“–”怎么办?
按照百度搜索资源平台<百度搜索网页标题规范:让标题回归标题本身>百度官方文档参考,如果WordPress标题里出现不规范"– 2.停止wptexturize转义任何字符 add_ ...
- sql server连接的基本使用(包含自身验证和连接Navicat)
自身验证的步骤 1.先默认使用Windows身份验证连接上SQL Server数据库 2.右键连接名称,选择属性 来到这个界面: 3.选中左侧的安全性 4.将服务器身份验证修改为SQL Server和 ...
- #HDU2255#奔小康赚大钱(KM模板题)
Problem Description传说在遥远的地方有一个非常富裕的村落,有一天,村长决定进行制度改革:重新分配房子.这可是一件大事,关系到人民的住房问题啊.村里共有n间房间,刚好有n家老百姓,考虑 ...
- CAS 单点登录系统
一.什么是单点登录 单点登录(Sign Sion On),简称为 SSO,是目前比较流行的企业业务整合的解决方案之一.SSO 的定义是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系 ...
- DFS总结
常见剪枝方法 优化搜索顺序 优先搜索决策树较小的点,例如在165. 小猫爬山一题中,优先搜索体重较大的扩展出的情况较少 排除冗余信息 如果某些情况在此前已经被搜索过了,那么无需继续搜索 可行性剪枝 如 ...
- .NET周报 【3月第4期 2023-03-24】
国内文章 .NET应用系统的国际化-多语言翻译服务 //www.cnblogs.com/tianqing/p/17232559.html 本文重点介绍了多语言翻译服务的设计和实现.文章描述 ...
- opencv-python 4.9.2. 轮廓特征
矩 图像的矩可帮助你计算某些特征,如对象的质心,对象的面积等特征.函数cv.moments()给出了计算的所有矩值的字典. 从这一刻起,你可以提取有用的数据,如面积,质心等.质心由关系给出, $$ C ...
- 用 Go 剑指 Offer 11. 旋转数组的最小数字
已知一个长度为 n 的数组,预先按照升序排列,经由 1 到 n 次 旋转 后,得到输入数组.例如,原数组 nums = [0,1,4,4,5,6,7] 在变化后可能得到:若旋转 4 次,则可以得到 [ ...
- day05-SpringCloud Eureka-服务注册与发现02
SpringCloud Eureka-服务注册与发现02 3.搭建EurekaServer集群-实现负载均衡&故障容错 3.1为什么需要集群EurekaServer? 微服务RPC远程服务调用 ...