SpringMVC源码探索之RequestBody的工作原理

遇到一个很奇怪的问题,后面发现了问题所在,原因是自己太过匆忙、连快捷键都被复制粘贴省略了。虽然出现问题的原因有点傻逼,但是之所以出现这种问题的原因却更加引人入胜。问题现象描述Controller中没有逻辑,只有一个@RequestBody注释的form表单然而这个TestBean有点特殊,非一般的g

遇到一个很奇怪的问题,后面发现了问题所在,原因是自己太过匆忙、连快捷键都被复制粘贴省略了。虽然出现问题的原因有点傻逼,但是之所以出现这种问题的原因却更加引人入胜。

问题现象描述

Controller中没有逻辑,只有一个@RequestBody注释的form表单

在这里插入图片描述

然而这个TestBean有点特殊,非一般的get/set方法

在这里插入图片描述

按照常理,发送post请求,body中塞入{"param":"hello"}即可成功注入到TestBean的实例中

在这里插入图片描述

高潮在后面,如果我改成{"paramA":"hello"},结果却能成功接收:

在这里插入图片描述

综上所述,paramA为之前定义的一个变量,我把它改成了新的变量param,然后顺便把paramA的get/set方法中的this.paramA改成了this.param。然后就出现了上面的情况。要知道,这个现象很反常,并且不常见。非常想知道为什么!

怎么通向背后的原因

从源码中看spring是如何处理@RequestBody

站在巨人肩膀上

从网上的参考资料来看,处理相关内容的源码可能位于一个叫做readWithMessageConverters()方法中。打开该方法所在的类(双击shift,然后输入方法名),大致是八九不离十的,如下:

在这里插入图片描述

打开至该方法后,使用debug模式,在可能的地方打上断点,发送postman请求,等待请求的到来:

在这里插入图片描述

接着往下看,寻找到一个能将JSON转换成对象的方法:

在这里插入图片描述

此时的messageConverters和body内容如下:

在这里插入图片描述

这里的read(),才是真正的开始。

向着jackson-databind的源码出发

跟着上面的read()方法,一路打断点达到了这里,这可以说是最终setter方法的执行处,代码如下:

加粗样式

所以,为什么setter方法会变成setParamA()呢? 目前为止,可行的解释为:jackson是根据已解析的出来的json的键,去寻找对应键的setter方法,与对应的Form表单类中的成员变量的值无关,因此也解释了刚开头那一幕戏剧性的现象。但是,源代码在哪里?

如何寻找对应的setter方法

经过一次又一次的打断点、跟踪代码运行,终于将规则锁定在了POJOPropertiesCollector.java这个类上了。这里面还涉及到了打断点、跟踪代码的一些技巧,一定要分清F7、F8、F9呀,不然就很容易就要从头再来。

to be continued...

从上面的代码来看,对TestForm的解析,变成了只有一个变量为paramA的bean类。所以,按照paramA的键,去JSON中取数据,然后调用对应的setter方法,最后赋值给了param,过程就是这样。

总结

对这个流程的跟踪,发现:只有第一次请求的时候,会初始化一个把JSON转化成对象的这样一个类,缓存使用的是LRUMap。其中desc是一个解析器,包含了变量与getter/setter方法之间的映射关系。关键的代码如下:

在这里插入图片描述

调试跟踪的过程中,主要的时间都花在了desc的初始化上。因为后续的调用,就是根据desc的映射关系去执行。

如何根据getter/setter去解析出成员变量名称?

其实这个流程并不难,我们自己也很容易想到,但是就是想看框架里面是怎么写的。在传给这个函数之前,已经保证了basename是以get开头,且此时的offset为3。

在这里插入图片描述

什么情况下删除param?

param即没有setter/getter方法的那个属性。这里因为param被private修饰,然后也未找到setter/getter方法。

在这里插入图片描述

具体判断是否可见的代码如下:

在这里插入图片描述

以上。于2019/01/31 00:51完成全部内容。

Read more

Volcano 与 Kubernetes GPU 调度学习笔记

本笔记系统整理 Volcano 调度器、Kubernetes 调度框架、GPU Device Plugin、HAMi 等云原生 AI 调度领域的核心知识,适合用于学习、复习和工程实践参考。 目录 * 第一部分:Volcano 入门 * 1. Volcano 是什么 * 2. 安装与快速使用 * 3. 核心特性一览 * 第二部分:Volcano 整体架构 * 4. Volcano 解决的核心问题 * 5. 整体架构与数据流 * 6. 三层抽象模型 * 第三部分:Volcano 核心实现原理 * 7. Session 机制 * 8. Gang Scheduling 实现 * 9. Queue 与 DRF 公平调度

容器镜像(4):镜像的常用工具箱

容器镜像(4):镜像的常用工具箱

前几篇在讲多架构镜像时已经用过 skopeo 和 crane 做镜像复制,这篇系统整理这两个工具的完整能力,同时介绍几个日常操作镜像时同样好用的工具。 一、skopeo:不依赖 Daemon 的镜像瑞士军刀 skopeo 的核心价值是绕过 Docker daemon,直接与 Registry API 交互。上一篇用它做镜像复制和离线传输,但它的能力远不止于此。 1.1 安装 # Ubuntu / Debian sudo apt install -y skopeo skopeo --version # skopeo version 1.15.1 1.2 inspect:免拉取检查镜像元数据 docker inspect 需要先把镜像拉到本地,skopeo inspect 直接向 Registry

容器镜像(3):多架构镜像构建

容器镜像(3):多架构镜像构建

一、什么是多架构镜像 1.1 OCI Image Index 上一篇介绍了单平台镜像的结构:一个 Manifest 指向 Config 和若干 Layer blob。多架构镜像在此之上多了一层——OCI Image Index(也叫 Manifest List),是一个轻量的索引文件,把多个单平台 Manifest 组织在一起: $ docker manifest inspect golang:1.22-alpine { "schemaVersion": 2, "mediaType": "application/vnd.oci.image.index.v1+json", "manifests&

容器镜像(2):containerd 视角下的镜像

容器镜像(2):containerd 视角下的镜像

一、为什么需要了解 containerd 如果你只用 docker run 跑容器,从来不关心底层,那可以不了解 containerd。但如果你在用 Kubernetes,或者想真正理解"容器运行时"是什么,containerd 是绕不开的。 事实上,当你执行 docker run 的时候,containerd 早就在后台悄悄工作了——Docker 从 1.11 版本开始,就把核心运行时剥离出来交给 containerd 负责。 1.1 Docker 的架构演变 早期的 Docker(1.10 及之前)是一个"大一统"的单体程序:一个 dockerd