目录
概述
深入细节
案例
@RequestBody与前端传过来的json数据的匹配规则
指定模型中的属性对应什么key
用@Valid校验@RequestBody的参数
根据RequestBody的内容来区分使用哪个资源
概述
- @RequestBody主要用来接收前端传递给后端的json字符串中的数据(请求体中的数据)
- 而最常用的使用请求体传参的无疑是POST请求了,所以使用@RequestBody接收数据时,一般都用POST方式进行提交
- 用途:用于获取HTTP请求的主体内容,通常用于读取非表单数据,如JSON或XML格式
- 数据绑定:将HTTP请求体中的数据绑定到一个对象上,通常使用转换器如Jackson或Gson将JSON或XML转换成Java对象
- 使用场景:适用于POST或PUT请求
- 举例:
深入细节
- (细节1) 在后端的同一个接收方法里,@RequestBody与@RequestParam()可以同时使用
- @RequestBody最多只能有一个,而@RequestParam()可以有多个
- (1)注意:一个请求,只有一个RequestBody;一个请求,可以有多个RequestParam
- (2)注意:当同时使用@RequestParam()和@RequestBody时,@RequestParam()指定的参数可以是普通元素、数组、集合、对象等等(即:当,@RequestBody与@RequestParam()可以同时使用时,原SpringMVC接收参数的机制不变,只不过RequestBody接收的是请求体里面的数据;而RequestParam接收的是key-value里面的参数,所以它会被切面进行处理从而可以用普通元素、数组、集合、对象等接收),即:如果参数是放在请求体中,application/json传入后台的话,那么后台要用@RequestBody才能接收到;如果不是放在请求体中的话,那么后台接收前台传过来的参数时,要用@RequestParam来接收,或者形参前什么也不写也能接收
- (3)注意:如果参数前写了@RequestParam(xxx),那么前端必须有对应的xxx名字才行(不管其是否有值,当然可以通过设置该注解的required属性来调节是否必须传),如果没有xxx名的话,那么请求会出错,报400
- (4)注意:如果参数前不写@RequestParam(xxx)的话,那么前端如果有xxx名的话,那么就会自动匹配;没有的话,请求也能正确发送
- 追注1:在Spring MVC中,当HTTP请求到达控制器的处理方法时,Spring会尝试将请求中的参数映射到方法的参数上;这是通过方法参数名称或注解提供的名称来进行的
- 追注2:如果控制器方法的参数前没有
@RequestParam
注解,Spring MVC会根据非限定名称来尝试绑定方法参数,这涉及到以下几点行为:
- 参数名称匹配:如果HTTP请求中有与方法参数同名的参数,Spring会自动将该值注入到方法参数中
- 可选参数:如果HTTP请求中缺少对应同名参数,而方法参数没有使用
@RequestParam
标记为必需,那么请求仍会被正确映射且方法会被调用;在这种情况下,该参数可能会被赋予一个null
值或基本类型的默认值(比如int
类型的0
);但这种行为可能会让方法中的参数接收到一个不是从请求直接提供的值,这可能会导致潜在的错误,除非有适当的空值处理逻辑 - 不明确的行为:如果没有
@RequestParam
明确指定参数,且请求中也没有同名参数,那么方法参数的值究竟是什么,可能不是那么明确;这可能取决于参数的类型及是否有默认构造方法等因素 - 参数类型转换:Spring会尝试将请求参数转换为方法参数的类型(例如,从字符串转换为整数);如果转换失败,可能会导致请求处理失败
- 下面是一个无
@RequestParam
注解的例子: - 在这个例子中,如果请求中有
category
这个参数,那么它的值会被传递到category
方法参数中 - 如果没有,该方法参数的值将为
null
(对于对象)或基本类型的默认值 - 综上所述,使用
@RequestParam
让你可以更明确地表达你期待的请求行为,包括参数是否必须、默认值是什么等 - 而不使用注解,则采取更隐式的绑定方式,参数可有可无,当它们存在时会被匹配和使用;不存在时,请求也能正确发送,但你的方法应当能处理这种情况
- 追注3:这里与feign消费服务时不同;feign消费服务时,如果参数前什么也不写,那么会被默认是@RequestBody的
- (细节2) 如果后端参数是一个对象,且该参数前是以@RequestBody修饰的,那么前端传递json参数时,必须满足以下要求:
- 后端@RequestBody注解对应的类在将HTTP的输入流(含请求体)装配到目标类(即:@RequestBody后面的类)时,会根据json字符串中的key来匹配对应实体类的属性,如果匹配一致且json中的该key对应的值符合(或可转换为)(这一条会在下面详细分析)实体类的对应属性的类型要求时,会调用实体类的setter方法将值赋给该属性
- json字符串中,如果value为""的话,后端对应属性如果是String类型的,那么接受到的就是"",如果后端属性的类型是Integer、Double等类型,那么接收到的就是null
- json字符串中,如果value为null的话,后端对应收到的就是null
- 如果某个参数没有value的话,在传json字符串给后端时,要么干脆就不把该字段写到json字符串中;要么写value时,必须有值,null或""都行;千万不能有类似"stature":,这样的写法,如:
案例
- 先给出两个等下要用到的实体类
- User实体类:
- Team实体类:
- @RequestBody直接以String接收前端传过来的json数据:
- 后端对应的Controller:
- 使用PostMan测试:
- @RequestBody以简单对象接收前端传过来的json数据:
- 后端对应的Controller:
- 使用PostMan测试:
- @RequestBody以复杂对象接收前端传过来的json数据:
- 后端对应的Controller:
- 使用PostMan测试(传递的json字符串中必须要有key,否则请求会出错):
- @RequestBody与简单的@RequestParam()同时使用:
- 后端对应的Controller:
- 使用PostMan测试:
- @RequestBody与复杂的@RequestParam()同时使用:
- 后端对应的Controller:
- 使用PostMan测试:
- @RequestBody接收请求体中的json数据;不加注解接收URL中的数据并组装为对象:
- 后端对应的Controller:
- 使用PostMan测试:
- 注意:如果在后端方法参数前,指定了@RequestParam()的话,那么前端必须要有对应字段才行(当然可以通过设置该注解的required属性来调节是否必须传),否则会报错;如果参数前没有任何该注解,那么前端可以传这个参数,也可以不传,如:
- 上图中,如果我们传参中没有指定token,那么请求能正常进去,但是token为null;如果在String token前指定了@RequestParam(“token”),那么前端必须要有token这个键,请求才能正常进去,否则报400错误
@RequestBody与前端传过来的json数据的匹配规则
- 声明:根据不同的Content-Type等情况,Spring-MVC会采取不同的HttpMessageConverter实现来进行信息转换解析
- 说明:request的body部分的数据编码格式由header部分的Content-Type指定
- 下面介绍的是最常用的:前端以Content-Type为application/json,传递json字符串数据;后端以@RequestBody模型接收数据的情况
- 解析json数据大体流程概述:Http传递请求体信息,最终会被封装进com.fasterxml.jackson.core.json.UTF8StreamJsonParser中(提示:Spring采用CharacterEncodingFilter设置了默认编码为UTF-8),然后在public class BeanDeserializer extends BeanDeserializerBase implements java.io.Serializable中,通过 public Object deserializeFromObject(JsonParser p, DeserializationContext ctxt) throws IOException方法进行解析
- 核心逻辑分析示例:
- 假设前端传的json串是这样的: {"name1":"兔子队列","age":20,"mot":"好焦虑"} 后端的模型只有name和age属性,以及对应的setter/getter方法
- 给出一般用到的deserializeFromObject(JsonParser p, DeserializationContext ctxt)方法的核心逻辑:
指定模型中的属性对应什么key
- 给出Controller中的测试类:
- 给出模型中的属性(setter/getter方法没截出来):
- 使用postman测试一下,示例:
- 解释1:@JsonAlias注解,实现:json转模型时,使json中的特定key能转化为特定的模型属性;但是模型转json时,对应的转换后的key仍然与属性名一致,见:上图示例中的name字段的请求与响应;以下图进一步说明:
- 此时,json字符串转换为模型时,json中key为Name或为name123或为name的都能识别
- 解释2:@JsonProperty注解,实现:json转模型时,使json中的特定key能转化为指定的模型属性;同样的,模型转json时,对应的转换后的key为指定的key,见:示例中的motto字段的请求与响应;以下图进一步说明:
- 此时,json字符串转换为模型时,key为MOTTO的能识别,但key为motto的不能识别
- 解释3:@JsonAlias注解需要依赖于setter、getter,而@JsonProperty注解不需要
- 解释4: 在不考虑上述两个注解的一般情况下,key与属性匹配时,默认大小写敏感
- 解释5: 有多个相同的key的json字符串中,转换为模型时,会以相同的几个key中,排在最后的那个key的值给模型属性复制,因为setter会覆盖原来的值;见示例中的gender属性
- 解释6: 后端@RequestBody注解对应的类在将HTTP的输入流(含请求体)装配到目标类(即:@RequestBody后面的类)时,会根据json字符串中的key来匹配对应实体类的属性,如果匹配一致且json中的该key对应的值符合(或可转换为)实体类的对应属性的类型要求时,会调用实体类的setter方法将值赋给该属性
用@Valid校验@RequestBody的参数
- 如果希望通过注解校验post请求的body,需要用到@Valid注解
- 在接收request实体类添加注解进行校验,例如用@NotNull进行判空校验
- 如果入参是List,单独用@Valid不生效,需要在Controller上加@Validated注解
- 结合统一异常处理,处理MethodArgumentNotValidException和ConstraintViolationException,可以返回注解配置的错误信息:
根据RequestBody的内容来区分使用哪个资源
- 如果使用Spring,可以通过@RequestBody将请求体的json转换为Java对象,但如果URI相同,而请求体的内容不同,应该怎么办?
- 问题来源(stackoverflow):
- Spring @RequestBody without using a pojo?
- 稍微研究了一下,如果将@RequestBody指定为Map,那么请求体(键、值)会存储到Map对象中
- 案例:
- 发送POST请求,虽然URI相同,但是请求体却不同
- 不过,携带了一个名为type的共同数据,并通过type的值来判别不同的情况
- 这次准备了两个type的值:concert和trip
- 控制器的实现:
- 在控制器的实现中,指定 @RequestBody 为 Map<String, Object> 类型
- 通过指定 Map,可以将请求体以键值对的形式存储
- 查看 type 键的值,以此判断是哪一种类型的请求,并将请求转换为相应的类
- 在转换过程中使用了 Jackson 的 ObjectMapper 类进行转换
- 这里,我们将其转换为相应类的对象,输出到标准输出并结束
- 尝试发送 concert 的请求
- 结果的标准输出
- 尝试发送 trip 的请求
- 结果的标准输出
- 所以正确地将其转换为对应的类是可能的
- 整体代码: