180730-Spring之RequestBody的使用姿势小结

Spring之RequestBody的使用姿势小结

SpringMVC中处理请求参数有好几种不同的方式,如我们常见的下面几种

  • 根据 HttpServletRequest 对象获取
  • 根据 @PathVariable 注解获取url参数
  • 根据 @RequestParam 注解获取请求参数
  • 根据Bean的方式获取请求参数
  • 根据 @ModelAttribute 注解获取请求参数

对上面几种方式有兴趣的可以看一下这篇博文: SpringMVC之请求参数的获取方式

除了上面的几种方式之外,还有一种 @RequestBody 的使用方式,本文则主要介绍这种传参的使用姿势和相关注意事项

I. 使用姿势

1. 服务接口

借助Spring框架,使用@RequestBody并没有什么难度,很简单的就可以写一个使用case出来,如下

@Slf4j
@RestController
public class ReqBodyController {
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public static class Req {
        private String key;
        private Integer size;
    }

    @RequestMapping(value = "/body", method = {RequestMethod.POST, RequestMethod.GET, RequestMethod.OPTIONS})
    public BaseRsp body(@RequestBody Req req) {
        log.info("req: {}", req);
        return new BaseRsp<>(req);
    }
}

看上面的实现,和我们通常的写法并无差别,无非是将以前的 @RequsetParam 注解换成 @RequsetBody 注解,而且这个注解内部只有一个filed,比RequsetParam还少

@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestBody {
    // 默认参数必须存在,否则会抛一个异常
    boolean required() default true;
}

看到上面的实现,估计也可以猜出,这个注解对于后端而言,写没啥问题,关键是如何用(具体来讲是如何给前端用)

2. 接口调用

上面写完了,接下来的重点就是如何使用了,在使用之前,有必要了解下 RequestBody 这个注解出现的原有以及应用场景(换句话说它和RequestParam有什么区别,为什么要单独的搞一个这个东西出来)

RequestBody

@requestBody注解常用来处理content-type不是默认的application/x-www-form-urlcoded编码的内容,比如说:application/json或者是application/xml等。一般情况下来说常用其来处理application/json类型。

a. content-type定义

在进入下一步之前,有必要说一下Content-Type这个http请求头的作用了,下面一段来自其他博文,原文链接见最后

MediaType,即是Internet Media Type,互联网媒体类型;也叫做MIME类型,在Http协议消息头中,使用Content-Type来表示具体请求中的媒体类型信息。

常见媒体格式如下:

  • text/html : HTML格式
  • text/plain :纯文本格式
  • text/xml : XML格式
  • image/gif :gif图片格式
  • image/jpeg :jpg图片格式
  • image/png:png图片格式

以application开头的媒体格式类型:

  • application/xhtml+xml :XHTML格式
  • application/xml : XML数据格式
  • application/atom+xml :Atom XML聚合格式
  • application/json : JSON数据格式
  • application/pdf :pdf格式
  • application/msword : Word文档格式
  • application/octet-stream : 二进制流数据(如常见的文件下载)
  • application/x-www-form-urlencoded : 中默认的encType,form表单数据被编码为key/value格式发送到服务器(表单默认的提交数据的格式)

b. content-type 实例说明

上面算是基本定义和取值,下面结合实例对典型的几种方式进行说明

  • application/x-www-form-urlencoded:数据被编码为名称/值对。这是标准的编码格式。
  • multipart/form-data: 数据被编码为一条消息,页上的每个控件对应消息中的一个部分。
  • text/plain: 数据以纯文本形式(text/json/xml/html)进行编码,其中不含任何控件或格式字符

对于前端使用而言,form表单的enctype属性为编码方式,常用有两种:application/x-www-form-urlencodedmultipart/form-data,默认为application/x-www-form-urlencoded

Get请求

发起Get请求时,浏览器用application/x-www-form-urlencoded方式,将表单数据转换成一个字符串(key1=value1&key2=value2...)拼接到url上,这就是我们常见的url带请求参数的情况

Post表单

发起post请求时,如果没有传文件,浏览器也是将form表单的数据封装成k=v的结果丢到http body中,拿开源中国的博客提交的表单为例,一个典型的post表单,上传的数据拼装在form data中,为kv结构

如果有传文件的场景,Content-Type类型会升级为multipart/form-data,这一块不详细展开,后面有机会再说

Post json串

post表单除了前面一种方式之外,还有一种也是我们常见的,就是讲所有的表单数据放在一个大的json串中,然后丢给后端,这里也有一个在线的实例,某电商平台的商品发表,截图如下

注意看上面的Request Payload,是一个大的json串,和前面差别明显

c. RequestBody请求

根据RequestBody的定义,要想访问前面定义的那个接口,使用传统的表单传递方式是不行的,curl命令测试如下

curl -X POST -d ‘key=haha&size=123‘ http://127.0.0.1:19533/body

后端对应的输出如下(抛了一个异常,表示@RequestBody注解修饰rest接口,不支持 Content type ‘application/x-www-form-urlencoded;charset=UTF-8‘

因此使用姿势需要显示添加请求头,传参也改变一下

curl -l -H "Content-type: application/json" -X GET -d ‘{"key": "!23", "size": 10}‘ http://127.0.0.1:19533/body

返回结果如下

3. 注意事项

a. content-type显示指定

根据前面的说明,可以知道 @RequestBody 这个注解的使用,使得REST接口接收的不再content-type为application/x-www-form-urlencoded的请求, 反而需要显示指定为application/json

b. 请求方法

RequestBody支持GET方法么?前面都是采用post提交参数,如果改成GET会怎样?

curl测试方式

curl -l -H "Content-type: application/json" -X GET -d ‘{"key": "!23", "size": 10}‘ http://127.0.0.1:19533/body\?key\=app

对应的后端debug截图如下,发现使用GET方式,并没有问题,依然可以获取到参数

换成大名鼎鼎的POSTMAN来测试

使用post方法请求时,截图如下,主要就是修改header的content-type,然后在body中添加json串格式的请求

然而改成get之后,body都直接灰掉了,也就是它不支持在get请求时,提交Body数据

url请求方式

接下来直接换成url的请求方式,看是否直接支持get请求

http://127.0.0.1:19533/body?{"key": "!23", "size": 10}

浏览器中输入时,服务器400, 换成curl方式请求,抛的是缺少RequestBody的异常,也就是说,将json串拼接到url中貌似不行(也有可能是我的使用姿势不对。。。)

小结

  • 到这里小结一下,使用RequestBody获取参数时,还是老老实实的选择POST方法比较合适,至于原因,跟大众,随主流,跟着大家的习惯走比较好

c. 参数获取

这个主要就是后端编写接口时,获取RequestBody参数的问题了,通过测试,发现在HttpServletRequest参数中,居然拿不到提交的RequestBody参数,演示如下

请求url为

curl -l -H "Content-type: application/json" -X POST -d ‘{"key": "!23", "size": 10}‘ http://127.0.0.1:19533/body\?url\=ddd

对应的debug截图如下,url参数可以拿到,RequestBody参数没有

首先声明,下面的这段分析,没有看源码,纯属于个人推断,如有问题,对被误导的朋友表示歉意,也希望对此有了解的朋友,多多批评指正

从传文件的思路出发,前端传文件给后端时,后端是基于流的方式,将上传的二进制流,写入到`MultipartFile`;而二进制流读完之后,没法再重复的读
RequestBody可能也是这么个逻辑,首先是从HttpServletRequest的Reader流中读取body参数并封装到上面的req对象,而不会像url参数一样,写回到`javax.servlet.ServletRequest#getParameterMap`

对上面的猜测做一个小小的验证,改成直接从HttpServletRequest的Reader流中获取请求body参数

@RequestMapping(value = "/body", method = {RequestMethod.POST, RequestMethod.GET, RequestMethod.OPTIONS})
public BaseRsp body(HttpServletRequest request) throws IOException {
    BufferedReader reader = request.getReader();
    StringBuilder builder = new StringBuilder();
    String line = reader.readLine();
    while (line != null) {
        builder.append(line);
        line = reader.readLine();
    }
    reader.close();

    String reqBody = builder.toString();
    Req req = JSON.parseObject(reqBody, Req.class);
    log.info("req: {}, request: {}", req, request.getParameterMap());
    return new BaseRsp<>(req);
}

验证如下

其实到这里,有个有意思的地方已经引起了我的好奇,那就是在Spring容器中HttpServletRequest这个东西,是怎么运转的,后面有机会再聊,此处不展开...

4. 小结

  • ReuqestBody 主要是处理json串格式的请求参数,要求使用方指定header content-type:application/json
  • RequestBody 通常要求调用方使用post请求
  • RequsetBody参数,不会放在HttpServletRequest的Map中,因此没法通过javax.servlet.ServletRequest#getParameter获取

II. 其他

0. 参考

1. 一灰灰Blog: https://liuyueyi.github.io/hexblog

一灰灰的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛

2. 声明

尽信书则不如,已上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现bug或者有更好的建议,欢迎批评指正,不吝感激

3. 扫描关注

小灰灰Blog&公众号

知识星球

原文地址:https://www.cnblogs.com/yihuihui/p/9392497.html

时间: 2024-10-09 22:13:04

180730-Spring之RequestBody的使用姿势小结的相关文章

spring mvc @RequestBody接受post json对象

jq ajax通过post json对象 遇到的HTTP 415/400问题错误解决方案:http://www.linuxidc.com/Linux/2014-04/99928.htm 客户端: <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</ti

spring mvc中的拦截器小结 .

在spring mvc中,拦截器其实比较简单了,下面简单小结并demo下. preHandle:预处理回调方法,实现处理器的预处理(如登录检查),第三个参数为响应的处理器(如我们上一章的Controller实现):      返回值:true表示继续流程(如调用下一个拦截器或处理器):              false表示流程中断(如登录检查失败),不会继续调用其他的拦截器或处理器,此时我们需要通过response来产生响应: postHandle:后处理回调方法,实现处理器的后处理(但在渲

SpringBoot基础篇AOP之基本使用姿势小结

一般来讲,谈到Spring的特性,绕不过去的就是DI(依赖注入)和AOP(切面),在将bean的系列中,说了DI的多种使用姿势:接下来看一下AOP的玩法 <!-- more --> I. 背景知识 在实际使用之前有必要了解一下什么是AOP,以及AOP的几个基本概念 1. advice before: 在方法执行之前被调用 after: 在方法执行之后调用 after returning: 方法执行成功之后 after throwing: 方法抛出异常之后 around: 环绕,自己在内部决定方

Spring单例与线程安全小结

转:http://www.cnblogs.com/doit8791/p/4093808.html 一.Spring单例模式与线程安全 Spring框架里的bean,或者说组件,获取实例的时候都是默认的单例模式,这是在多线程开发的时候要尤其注意的地方. 单例模式的意思就是只有一个实例.单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例.这个类称为单例类. 当多用户同时请求一个服务时,容器会给每一个请求分配一个线程,这是多个线程会并发执行该请求多对应的业务逻辑(成员方法),此时

《Java从入门到放弃》入门篇:spring中IOC的注入姿势

IOC到底是个什么东东呢?控制反转(Inversion of Control,英文缩写为IoC),其实就是这个东东. 你随便百度一下就会得到比较书面的解释:通过引入实现了IoC模式的IoC容器,即可由IoC容器来管理对象的生命周期.依赖关系等,从而使得应用程序的配置和依赖性规范与实际的应用程序代码分开.其中一个特点就是通过文本的配置文件进行应用程序组件间相互关系的配置,而不用重新修改并编译具体的代码. 说了这么多,通过一个例子就能很好的来理解.以前小王要找对象,要么云茫茫人海中进行匹配(通过 小

Spring注解与Java元注解小结

注解 Annotation 基于注解的开发,使得代码简洁,可读性高,简化的配置的同时也提高了开发的效率,尤其是SpringBoot的兴起,随着起步依赖和自动配置的完善,更是将基于注解的开发推到了新的高度. 元注解 meta-annotation Java 5 定义了四个标准的元注解类型,用以提供对其它注解的功能说明. 位于java.lang.annotation包下,分别为: 1. @Target 2. @Retention 3. @Documented 4. @Inherited 以@Prof

转-Spring 注解学习手札(七) 补遗——@ResponseBody,@RequestBody,@PathVariable

转-http://snowolf.iteye.com/blog/1628861/ Spring 注解学习手札(七) 补遗——@ResponseBody,@RequestBody,@PathVariable 博客分类: Spring spring@ResponseBody@RequestBody@PathVariableHttpMessageConverter 最近需要做些接口服务,服务协议定为JSON,为了整合在Spring中,一开始确实费了很大的劲,经朋友提醒才发现,SpringMVC已经强悍

Spring核心概念

Spring IoC Spring IoC就是控制反转,也被称为依赖注入(Dependency Injection, DI),是面向对象编程中的一种设计理念,用来降低程序代码之间的耦合度. 依赖是什么: 依赖就是在代码中通过局部变量.方法参数.返回值等建立的对于其他对象的调用关系. 1 /** 2 * @content 接口 3 * @author Gawain 4 * @date 2017-8-15下午8:02:37 5 */ 6 public interface DependDemo { 7

spring学习——Ioc基础二

一.IoC容器的概念 IoC容器就是具有依赖注入功能的容器,IoC容器负责实例化.定位.配置应用程序中的对象及建立这些对象间的依赖.应用程序无需直接在代码中new相关的对象,应用程序由IoC容器进行组装.在Spring中BeanFactory是IoC容器的实际代表者. Spring IoC容器如何知道哪些是它管理的对象呢?这就需要配置文件,Spring IoC容器通过读取配置文件中的配置元数据,通过元数据对应用中的各个对象进行实例化及装配.一般使用基于xml配置文件进行配置元数据,而且Sprin