spring中,为什么前端明明传了值,后端却接收不到

这篇具有很好参考价值的文章主要介绍了spring中,为什么前端明明传了值,后端却接收不到。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

问题场景

在进行前后端的联调时,有时候会出现,前端明明传了值,后端接口却接收不到的情况,这种情况常常让人很苦恼,然后就会去仔细对比前后端的参数单词是不是对应上了,也会去检查是不是前端的请求参数格式有问题,又或者是后端接口接收的参数格式有问题,一通检查对比下来,发现都没问题。那究竟是为什么呢?那就继续往下看吧。


问题重现

控制层代码:

    @PostMapping(value = "/test")
    public void test(@RequestBody UserVO userVO) {

        System.out.println("用户代码:" + userVO.getUCode());
        System.out.println("用户名称:" + userVO.getUName());

    }

参数实体类:UserVO

@Data
public class UserVO {

    /**
     * 用户代码
     */
    private Long uCode;

    /**
     * 用户名称
     */
    private String uName;

}

用postman模拟前端调用:

后台实体类接收不到前端传的值,Java,开发常见问题,技术分享,java,spring boot,spring,前端
控制台预期打印结果:

用户代码:12345
用户名称:小明

控制台实际打印结果:
后台实体类接收不到前端传的值,Java,开发常见问题,技术分享,java,spring boot,spring,前端


解决方式

在实体类的属性上方加@JsonProperty注解,如下图:

后台实体类接收不到前端传的值,Java,开发常见问题,技术分享,java,spring boot,spring,前端

然后测试控制台打印结果:
后台实体类接收不到前端传的值,Java,开发常见问题,技术分享,java,spring boot,spring,前端


原因分析

首先我们先把实体类复原,并且加上一个新的属性loginType

@Data
public class UserVO {

    /**
     * 用户代码
     */
    private Long uCode;

    /**
     * 用户名称
     */
    private String uName;

    /**
     * 登录类型
     */
    private String loginType;

}

眼尖的同学可能会发现了,我新加的属性loginType长得是不是跟原来两个属性uCode和uName不太一样,不一样的点在于uCode和uName都是首字母小写,第二个字母大写的单词,而loginType则不然。但是它们三都符合驼峰命名法的规范,对吧。这时候可以猜测,难道是这个原因导致的?

在这里我们先来简单验证下uCode、uName、loginType的情况

后台实体类接收不到前端传的值,Java,开发常见问题,技术分享,java,spring boot,spring,前端

后台实体类接收不到前端传的值,Java,开发常见问题,技术分享,java,spring boot,spring,前端
通过断点发现,uCode、uName是空的,loginType却不是空的
然后我们将uCode、uName分别改为userCode、userName后再进行测试

@Data
public class UserVO {

    /**
     * 用户代码
     */
    private Long userCode;

    /**
     * 用户名称
     */
    private String userName;

    /**
     * 登录类型
     */
    private String loginType;

}

后台实体类接收不到前端传的值,Java,开发常见问题,技术分享,java,spring boot,spring,前端

后台实体类接收不到前端传的值,Java,开发常见问题,技术分享,java,spring boot,spring,前端

这个时候我们就可以得出结论,原因就是首字母小写,第二个字母大写的单词的属性是有问题的。

但是我们不禁要问,为啥呢?它这也符合驼峰命名法的规范啊。为什么它就有问题呢?感兴趣的同学可以接着往下看。


原理分析

首先我们要知道,在Spring中,前后端之间数据传输会涉及到数据的序列化和反序列化的操作,并且SpringBoot默认是使用Jackson作为JSON数据格式处理的类库。

序列化:按照指定的格式、顺序等将实体类对象转换为JSON对象;
反序列化:将JSON对象中的字符串、数字等,将其转换为实体对象;

那么现在咱们就来断点调试Jackson的源码来看看原因。为方便展示,我将实体类留下uName、loginType两个属性

@Data
public class UserVO {

    /**
     * 用户名称
     */
    private String uName;

    /**
     * 登录类型
     */
    private String loginType;

}

开始调试:
Jackon主要是通过抽象类AbstractJackson2HttpMessageConverterreadJavaType方法将 HTTP 请求中的消息体转换为对象,所以我们找到这部分代码,对他进行断点调试:

后台实体类接收不到前端传的值,Java,开发常见问题,技术分享,java,spring boot,spring,前端
然后逐步断点,在上图的第192行和第195行,它会调用ObjectMapper.readValue,然后断点推进到调用方法的核心地方ObjectMapper_readMapAndClose方法

后台实体类接收不到前端传的值,Java,开发常见问题,技术分享,java,spring boot,spring,前端
this._findRootDeserializer(ctxt, valueType);的大概意思就是根据类型找到反序列化器,注意在这边是先从缓存中取,取到了的话就直接返回了。如果没到下一步断点,在这边你可以清除一下缓存。
后台实体类接收不到前端传的值,Java,开发常见问题,技术分享,java,spring boot,spring,前端
然后断点继续推进到创建反序列化器的地方DeserializerCache._createDeserializer

如果你清除缓存或者重启项目在调用时会直接进入到这个创建反序列化器的地方,你直接在这个方法上打断点就好了

后台实体类接收不到前端传的值,Java,开发常见问题,技术分享,java,spring boot,spring,前端
找到上图中第164行的代码,BeanDescription是类的描述的意思,所有的属性都在这里被解析,然后我们断点进去看看。会进入到POJOPropertiesCollector.collectAll方法,就是字面意思,收集所有。方法逻辑详见下图:
后台实体类接收不到前端传的值,Java,开发常见问题,技术分享,java,spring boot,spring,前端
执行完this._addFields(props);props加入了uNameloginType

后台实体类接收不到前端传的值,Java,开发常见问题,技术分享,java,spring boot,spring,前端
执行完this._addMethods(props);后发现props竟然多了一个uname

后台实体类接收不到前端传的值,Java,开发常见问题,技术分享,java,spring boot,spring,前端

在这里我们点开属性详细去看,会发现uName的get和set为空,但是loginType的是正常的,并且uname这个不知道哪里跑出来的属性的get和set也是不为空的。

后台实体类接收不到前端传的值,Java,开发常见问题,技术分享,java,spring boot,spring,前端
后台实体类接收不到前端传的值,Java,开发常见问题,技术分享,java,spring boot,spring,前端

再接着执行this._removeUnwantedProperties(props);移除不想要的属性之后,会发现就剩下loginTypeuname了,因为uName没有get和set。为什么
后台实体类接收不到前端传的值,Java,开发常见问题,技术分享,java,spring boot,spring,前端
然后props中目前存储的就是loginTypeuname

现在我们就要弄明白为什么有get/set的是uname而不是uName

首先,在这个例子中我使用的是@Data注解,也就是使用的 Lombok,也就是说 getter 和 setter 是由 Lombok 生成的。使用注解的话会将get/set方法隐藏起来,然后我们可以通过IDEA的Structure来看,见下图:

后台实体类接收不到前端传的值,Java,开发常见问题,技术分享,java,spring boot,spring,前端

那么Jackson 到底是如何解析的,使得解析出来的是uname,而不是uName。它解析的具体代码在com.fasterxml.jackson.databind.util.BeanUtil类中的legacyManglePropertyName方法中
后台实体类接收不到前端传的值,Java,开发常见问题,技术分享,java,spring boot,spring,前端

从上图为我们可以很明显的看到,通过这个方法之后getLoginType被解析成loginType了。那我们再来看看uName,见下图:
后台实体类接收不到前端传的值,Java,开发常见问题,技术分享,java,spring boot,spring,前端
从上图断点我们可以清晰的看见getUName被解析成uname了,按照我们正常的思维逻辑的话,loginType和uName都符合驼峰命名法的规范,那么uName对应的get方法解析出来应该是uName啊,为什么变成了uname呢?原因就在于这个legacyManglePropertyName方法的处理逻辑,它的逻辑大概是:

1.根据入参offset去除get或者get,然后就剩下UName或者LoginType了
2.然后从第一个字母开始解析,如果第一个字母是大写的,于是就将它转成小写,然后找下一个,如果还是大写,就继续转成小写,直到找到一个小写字母后,就把之后的字母(不管大小写)一起拼接进来。

这样就能解释了:

去除get之后的LoginType找到第一个字母是大写,转为小写的l,下一个字母是小写的了,就直接把后面的全拼接进来,最终形成了loginType

去除get之后的UName找到第一个字母是大写,转为小写的u,下一个字母又是大写,转为小写的n,在下一个字母是小写的了,就直接把后面的全拼接进来,最终形成了uname

如果说这边的getUName换成getuName,那么解析出来的就是正确的uName了。

结论

到这里,我们就可以得出结论了

因为 Lombok 生成 get、set 方法的语义规范与和Jackson 处理 get、set 方法之间的不一致,导致属性名无法匹配上,最终也就导致了前端明明传了参数,后端却接收不到的问题。

扩展

我后面去github的 lombok社区 了解了相关内容,lombook社区是这样描述的:

后台实体类接收不到前端传的值,Java,开发常见问题,技术分享,java,spring boot,spring,前端
用网页翻译给他翻译成中文,翻译有些不对,但是能看明白大概意思就行

后台实体类接收不到前端传的值,Java,开发常见问题,技术分享,java,spring boot,spring,前端

lombok的大概意思就是:我就是这样的规范,即使其他的工具框架都改了,我也不改,但是建议你们不要使用首字母小写第二个字母大写的属性名,避免出现问题,可能知名度比较高的框架都比较傲娇吧哈哈。

但是lombok还是给出了一个解决方案,加上这个配置项

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

其中basic代表遵循lombok的规范(getUName);beanspec代表遵循Spring、Jackson 的规范(getuName)。默认是basic。

看到这里,我就来总结一下能解决这个问题的三种方案吧

1. 加@JsonProperty注解强行指定属性名

@Data
public class UserVO {

    /**
     * 用户名称
     */
    @JsonProperty(value = "uName")
    private String uName;

    /**
     * 登录类型
     */
    private String loginType;

}

2.不使用lombok,使用IDEA默认生成get/set方法

后台实体类接收不到前端传的值,Java,开发常见问题,技术分享,java,spring boot,spring,前端
后台实体类接收不到前端传的值,Java,开发常见问题,技术分享,java,spring boot,spring,前端
后台实体类接收不到前端传的值,Java,开发常见问题,技术分享,java,spring boot,spring,前端
后台实体类接收不到前端传的值,Java,开发常见问题,技术分享,java,spring boot,spring,前端

3.加上lombok配置项

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

最后,博主的建议是,尽量不要用这种命名方式,如果非要用,那就加上@JsonProperty注解强行指定属性名,这样比较方便。文章来源地址https://www.toymoban.com/news/detail-768147.html

到了这里,关于spring中,为什么前端明明传了值,后端却接收不到的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

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

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

相关文章

  • 为什么 OpenAI 团队采用 Python 开发他们的后端服务?

    Python,年龄可能比很多读者都要大,但是它在更新快速的编程界却一直表现出色,甚至有人把它比作是编程界的《葵花宝典》,只是Python的速成之法相较《葵花宝典》有过之而无不及。 Python简洁,高效的特点,大大提升了程序员的编码速度,极大的提高了程序员的办公效率,

    2024年02月02日
    浏览(39)
  • Python爬虫:从后端分析为什么你爬虫爬取不到数据

    仅仅是小编总结的三点而已,可能不是很全面,如果之后小编了解到新的知识点,可能还会增加的哈! 1. 最简单的爬虫代码 也就是各位最常使用的,直接利用requests模块访问当前网站链接,利用相关解析模块从而获取得到自己想要的数据,如下(利用python爬虫爬取自己csdn个人

    2024年02月09日
    浏览(49)
  • 什么是 Spring?为什么学它?

    欢迎来到本篇文章!在这里,我将带领大家快速学习 Spring 的基本概念,并解答两个关键问题:什么是 Spring,以及为什么学习 Spring。 废话少说,下面,我们开始吧! Spring 官方文档:https://docs.spring.io/spring-framework/docs/5.2.24.RELEASE/spring-framework-reference/ 简化我们的企业级应用程

    2024年02月06日
    浏览(38)
  • 前端为什么发请求没有携带cookie?

    在前端发送请求时,如果想要携带 cookie,通常只能携带存储在与请求域名相同路径的 cookie。这是由浏览器的同源策略所决定的。 同源策略要求请求的域名、协议和端口都必须一致,否则浏览器会限制跨域请求的权限。当浏览器发送跨域请求时,默认情况下不会自动携带 co

    2024年02月06日
    浏览(83)
  • 《Go语言在微服务中的崛起:为什么Go是下一个后端之星?》

    🌷🍁 博主猫头虎🐅🐾 带您进入 Golang 语言的新世界✨✨🍁 🦄 博客首页 ——🐅🐾猫头虎的博客🎐 🐳 《面试题大全专栏》 🦕 文章图文并茂🦖生动形象🐅简单易学!欢迎大家来踩踩~🌺 🌊 《IDEA开发秘籍专栏》 🐾 学会IDEA常用操作,工作效率翻倍~💐 🌊 《100天精通

    2024年02月09日
    浏览(75)
  • 后端服务器的响应为什么能够精准的返回给请求它的那个客户端

    正向代理位于客户端和目标服务器之间,代表客户端发送请求;–》代理客户端 反向代理位于目标服务器和客户端之间,代表目标服务器接收请求。–》代理服务端 当客户端发送请求到反向代理服务器时,反向代理服务器会在转发请求给后端服务器之前,记录请求的源IP地址

    2024年02月12日
    浏览(40)
  • Spring为什么默认是单例的?

    目录 一、五种作用域 二、单例bean与原型bean的区别  三、单例Bean的优势与劣势 一、五种作用域 1.singleton: singleton是Spring Bean的 默认作用域 ,也就是单例模式。在整个应用程序中,只会创建一个实例,Bean的所有请求都会共享这个实例。 2.prototype: prototype表示 原型模式 ,每次

    2024年02月07日
    浏览(31)
  • 【Spring】浅谈spring为什么推荐使用构造器注入

    因本人实力有限,该文章主要内容(在文章基础上加了点点东西)均来自: 原文链接:https://www.cnblogs.com/joemsu/p/7688307.html 作者:joemsu ​ Spring框架对Java开发的重要性不言而喻,其核心特性就是IOC(Inversion of Control, 控制反转)和AOP,平时使用最多的就是其中的IOC,我们通过

    2024年02月13日
    浏览(38)
  • 有了Spring为什么还需要SpringBoot呢

    目录 一、Spring缺点分析 二、什么是Spring Boot 三、Spring Boot的核心功能 3.1 起步依赖 3.2 自动装配 1. 配置文件和依赖太多了!!! spring是一个非常优秀的轻量级框架,以IOC(控制反转)和AOP(面向切面)为思想内核,极大简化了JAVA企业级项目的开发。虽然Spring的组件代码是轻

    2024年02月08日
    浏览(41)
  • 31、Spring容器启动时,为什么先加载BeanFactoryPostProcess

    因为BeanDefinition会在ioc容器加载的时候先注册, 而BeanFactoryPostProcess就是在所有的BeanDefinition注册完后做扩展的,所以要先加载BeanFactoryPostProcess 解析配置类的组件 它就实现BeanFactoryPostProcess, 所以要先去加载BeanFactoryPostProcess 方式一:通过BeanFactory获取 方式二 :通过BeanFactor

    2024年02月04日
    浏览(38)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包