相关推荐recommended
SpringBoot接口入参校验的几种方式
作者:mmseoamin日期:2024-04-27

前言部分主要是一些说明,会有点啰嗦,若是着急,自行选择跳过。

0. 前言

日常业务开发中,有时不可避免的需要进行请求参数的校验,对于一些与业务无关的、简单的参数校验(如非空校验、长度限制等),则可以采用javax.validationhibernate-validator通过注解方式实现校验。

web开发中,前端的参数校验是为了用户体验,而后端的参数校验更多是为了安全,防止非法参数对业务造成影响。

此外,入参校验其实也是后端代码规范的一个重要体现。

1)@Validated@Valid的区别

SpringBoot中常用的校验注解是@Validated@Valid@Validated是对@Valid进行了二次封装,它们的区别如下

不同@Valid@Validated
来源Hibernate validation 提供的校验注解Spring Validator 提供的校验注解,是 Hibernate validation 基础上的增强版
注解位置可用在构造函数、方法、方法参数 和 成员属性上用在 类、方法和方法参数上。但不能用于成员属性
嵌套验证可用在级联对象的成员属性上面(后面会有使用示例)不支持
分组无此功能提供分组功能,可以在入参验证时,根据不同的分组采用不同的验证机制

可以看到,@Validated和@Valid两者功能上最大的不同就是嵌套验证和分组的功能,除此之外,它们的功能差不多。因此在下面的代码示例中,将发现有些地方用@Validated或@Valid,实现的入参校验效果是一样的。

2)Hibernate validationSpring Validator区别

  1. javax的validation是Java定义的一套基于注解的数据校验规范,目前已经从JSR 303的1.0版本升级到JSR 349的1.1版本,再到JSR 380的2.0版本(2.0完成于2017.08),已经经历了三个版本。

    需要注意的是,Java API规范 (JSR303) 只是定义了Bean校验的标准validation-api,它规定了一些校验注解的规范,但没有提供实现!比如@Null、@NotNull、@Pattern等,它们位于 javax.validation.constraints这个包下。

  2. Hibernate validation是对这个规范的实现,并增加了一些其他校验注解,如 @NotBlank、@NotEmpty、@Length等,它们位于org.hibernate.validator.constraints这个包下。
  3. Spring Validator又是对Hibernate validation的二次封装,用于支持spring mvc参数自动校验。

3)常用的校验注解

这里主要介绍在SpringBoot中的几种参数校验方式。常用的用于参数校验的注解如下:

注解说明适用类型
@Null所注解的元素值为null所有对象
@NotNul所注解的元素值不能为null(注意:postman测试时,无法发送null的参数)所有对象
@NotBlank被注解的元素不能为null且trim()之后的size大于0String
@NotEmpty被注解的元素不能为null或者长度为0的(String Collection Map的isEmpty()方法)String、集合、数组
@Size所注解元素的长度大小需保证在给定范围[n,m]之内,如@Size(min=1, max=10)String、集合、数组
@AssertFalse所注解的元素值为falseBoolean
@AssertTrue所注解的元素值为trueBoolean
@Past所注解的元素必须是过去的日期日期
@PastOrPresent所注解的元素必须是过去或现在日期日期
@Future所注解的元素必须是将来的日期日期
@FutureOrPresent所注解的元素必须是现在或将来的日期日期
@Digits所注解的元素的值必须是指定的位数。如@Digits(integer=3,fraction=2)表示数字最多可以有3位整数部分,最多可以有2位小数部分数字
@Min所注解的元素必须小于等于给定的值数字
@Max所注解的元素必须大于等于给定的值数字
@DecimalMin所注解的元素必须大于等于给定的浮点数数字
@DecimalMax所注解的元素必须小于等于给定的浮点数`数字
@Negative所注解的元素必须是负整数数字
@NegativeOrZero所注解的元素必须是负整数或零数字
@Positive所注解的元素必须是正整数数字
@PositiveOrZero所注解的元素必须是正整数或零数字
@Range检查数字是否在范围之间,如@Range(min=1, max=100, message="ID必须在1到100之间")数字
@Pattern所注解的元素必须满足给定的正则表达式String
@Email所注解的元素需满足Email格式String

4)接口定义方式

下文将结合接口的几种定义方式,来看如何在定义SpringBoot接口的时候,做参数校验。

在文章【SpringBoot接收接口入参的几种方式】中,是从调用方的角度来看在调用接口时有哪几种传参方式。本文则是从接口定义方式的角度,去看看如何在Controller中书写接口方法,同时合理添加对应的参数校验。

  1. 接口定义方式1:在方法形参的位置,把每个参数平铺开来
  2. 接口定义方式2:用占位符的方式把入参封装到请求路径中
  3. 接口定义方式3:把入参封装到一个实体中
  4. 接口定义方式4:用原生的HttpServletRequest接收参数
  5. 接口定义方式5:用Map来接收入参

由于定义方式4和方式5,都不方便结合@Validated或@Valid进行入参的校验,所以本文将在最后进行简单的介绍即可。

5)关于代码的说明

5.1) 如何引入校验的相关pom依赖?

如果SpringBoot版本小于2.3.x,spring-boot-starter-web会自动传入hibernate-validator依赖(实测2.0.4.RELEASE和2.1.9.RELEASE版本是这样的)。如果Springboot版本大于2.3.x,则需要手动引入依赖。


        
            javax.validation
            validation-api
            2.0.1.Final
        

        
            org.hibernate.validator
            hibernate-validator
            6.2.0.Final
        

或者,直接在pom.xml中引入springboot的starter场景启动器(推荐这种方式):

	
            org.springframework.boot
            spring-boot-starter-validation
    

5.2)关于入参校验时的异常捕获

在接口定义时如果加入了入参校验,那么当调用方在调用接口时如果传入的参数不符合校验规则,一般来说后端代码是会抛出相应的异常的,并且返回给到调用方的结果也会不太友好,如下所示:

SpringBoot接口入参校验的几种方式,在这里插入图片描述,第1张

从后端代码的控制台,可以看到抛出了相应的异常,下图是示例:

SpringBoot接口入参校验的几种方式,在这里插入图片描述,第2张

如果你希望你的接口无论如何都返回一个友好的返回结果格式,如下所示:

SpringBoot接口入参校验的几种方式,在这里插入图片描述,第3张

想要实现这样的效果,可以利用@ControllerAdvice+@ExceptionHandler的方式,捕获对应的异常,然后在捕获异常的方法内返回相应的ResultVO对象。

下面是我利用@ControllerAdvice+@ExceptionHandler,做的关于入参校验的异常捕获处理,仅供参考:

package com.su.demo.exception;
import com.su.demo.bean.dto.*;
import com.su.demo.bean.vo.base.ResultCodeEnum;
import com.su.demo.bean.vo.base.ResultVO;
import com.su.demo.controller.param_validate.ParameterValidateController;
import com.su.demo.controller.param_validate.CheckParamController2;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolation;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
/**
 * 

用统一异常处理来返回一个更友好的提示 *

比如我们系统要求无论发送什么异常,http的状态码必须返回200,由业务码去区分系统的异常情况。 * *

当参数校验异常的时候,该统一异常处理类在控制台打印信息的同时把bad request的字符串和HttpStatus.BAD_REQUEST所表示的状态码400返回给调用方(用@ResponseBody注解实现,表示该方法的返回结果直接写入HTTP response body 中)。其中: *

    *
  • {@link ControllerAdvice}控制器增强,使@ExceptionHandler、@InitBinder、@ModelAttribute注解的方法应用到所有的 @RequestMapping注解的方法。
  • *
  • {@link ExceptionHandler}异常处理器,此注解的作用是当出现其定义的异常时进行处理的方法,此例中处理ValidationException异常。
  • *
* * */ @RestControllerAdvice public class GlobalControllerExceptionHandler { // 参数校验异常1 /** *

该ExceptionHandler对应的情况:接口定义处添加了如{@link javax.validation.constraints.NotNull}等校验注解,但是调用接口时却没有传参 *

此时接口会报{@link javax.validation.ConstraintViolationException}异常且返回如下的 * 不友好的结果 给接口调用方 *

{ *

"timestamp": "2024-01-31T06:07:15.183+00:00", *

"status": 500, *

"error": "Internal Server Error", *

"path": "/param_validate/parameter/test2" *

} *

参考接口:{@link ParameterValidateController#test2(String, Integer, Date, HttpServletRequest)} */ @ExceptionHandler(javax.validation.ConstraintViolationException.class) @ResponseStatus(HttpStatus.BAD_REQUEST) // 加上这个之后,返回的状态码就不是200了,而是400 Bad Request public ResultVO constrainViolationException(javax.validation.ConstraintViolationException e) { // return e.getMessage(); // 这种方式只返回第一个错误(其实也可以) // 下面这种方式,可以返回所有字段的查检不通过的结果 String res = e.getConstraintViolations().stream() .map(ConstraintViolation::getMessage) .collect(Collectors.joining(";")); return new ResultVO(ResultCodeEnum.IllEGAL_PARAMETER, res); } /** *

该ExceptionHandler对应的情况,当如下接口定义中必填参数不传或者传入的参数格式错误时,报当前exception *

参考接口:{@link com.su.demo.controller.param_validate.EntityValidateController#test2_validated_requestbody(User11, HttpServletRequest)} */ @ExceptionHandler(org.springframework.web.bind.MethodArgumentNotValidException.class) @ResponseStatus(HttpStatus.BAD_REQUEST) public ResultVO methodArgNotValidException(org.springframework.web.bind.MethodArgumentNotValidException e) { BindingResult bindingResult = e.getBindingResult(); if (!bindingResult.hasErrors()) { e.printStackTrace(); return new ResultVO<>(ResultCodeEnum.IllEGAL_PARAMETER, "系统异常,入参校验失败"); } /**方式 1 */ // return "接口入参校验2:" + bindingResult.getFieldError().getDefaultMessage(); // 这种方式只返回第一个错误(其实也可以) /**方式 2 */ // 下面这种方式,可以返回所有字段的查检不通过的结果 // String res = bindingResult.getAllErrors().stream() // .map(ObjectError::getDefaultMessage) // .collect(Collectors.joining(";")); // return "接口入参校验2:" + res; /**方式 3 */ String res2 = bindingResult.getFieldErrors().stream() .map(fieldError -> fieldError.getField() + ": " + fieldError.getDefaultMessage()) .collect(Collectors.joining("; ")); return new ResultVO<>(ResultCodeEnum.IllEGAL_PARAMETER, res2); } /** *

该ExceptionHandler对应的情况:调用接口时请求类型错误 *

此时如果不进行对应的异常处理,则会报{@link org.springframework.web.HttpRequestMethodNotSupportedException}异常且返回如下的 * 不友好的结果 给接口调用方 *

{ *

"timestamp": "2024-01-02T09:06:04.511+0000", *

"status": 405, *

"error": "Method Not Allowed", *

"message": "Request method 'POST' not supported", *

"path": "/test_response_advice/string" *

} */ @ExceptionHandler(HttpRequestMethodNotSupportedException.class) @ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED) public ResultVO methodNotSupportException(HttpRequestMethodNotSupportedException e) { return new ResultVO(ResultCodeEnum.METHOD_NOT_ALLOWED, "接口仅支持如下请求方式: " + Arrays.toString(e.getSupportedMethods())); } /** *

该ExceptionHandler对应的情况:在调用【入参为格式requestBody】的接口时: *

    *
  • 1) 不传body请求体
  • *
  • 2) 或传的body请求体格式错误,如:{ "age": 130, ""}
  • *
  • 3) 或传的body请求体内的日期格式错误
  • *
* 此时如果不进行对应的异常处理,则会报{@link org.springframework.http.converter.HttpMessageNotReadableException}异常且返回如下的 *

参考方法 {@link com.su.demo.controller.param_validate.EntityValidateController#test3_valid_requestbody(User11, HttpServletRequest)} */ @ExceptionHandler(HttpMessageNotReadableException.class) @ResponseStatus(HttpStatus.BAD_REQUEST) public ResultVO httpMsgNotReadableExp(HttpMessageNotReadableException e) { return new ResultVO(ResultCodeEnum.IllEGAL_PARAMETER, e.getMessage()); } /** *

该ExceptionHandler对应的情况:在调用【入参设置为如(@RequestParam("name") String name)】的接口时 ,入参name却不传(为null)的时候报的异常 *

因为 {@link RequestParam} 注解的required默认是true,因此被这个注解标记的入参如果为空, * 则会报{@link org.springframework.web.bind.MissingServletRequestParameterException}异常 *

如果不做此异常捕获处理,则接口返回如下的 不友好的结果 给调用方: *

{ *

"timestamp": "2023-12-29T03:10:57.961+0000", *

"status": 400, *

"error": "Bad Request", *

"message": "Required String parameter 'name' is not present", *

"path": "/param_type/get_reqestparam" *

} *

参考方法: {@link ParameterValidateController#test1(String, Integer, HttpServletRequest)} */ @ExceptionHandler(org.springframework.web.bind.MissingServletRequestParameterException.class) @ResponseStatus(HttpStatus.BAD_REQUEST) public ResultVO missingRequestParamException(MissingServletRequestParameterException e) { e.printStackTrace(); return new ResultVO(ResultCodeEnum.IllEGAL_PARAMETER, e.getMessage()); } /** *

该ExceptionHandler对应的是类似如下的情况入参类型错误: *

    *
  • 1) 字段age类型是Integer,但是入参却是test这样的字符串
  • *
  • 2) 字段enable类型是Boolean,但是入参却是666这样的数值
  • *
  • 3) 字段birth类型是Date且@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss"),但是入参却是20230101这样的错误格式
  • *
  • 4) 等等
  • *
*

参考方法: {@link ParameterValidateController#test2(String, Integer, Date, HttpServletRequest)} */ @ExceptionHandler(org.springframework.web.method.annotation.MethodArgumentTypeMismatchException.class) @ResponseStatus(HttpStatus.BAD_REQUEST) public ResultVO methodArgTypeMismatchException(MethodArgumentTypeMismatchException e) { // e.printStackTrace(); String fieldName = e.getName(); String errorStr = e.getCause().toString(); HashMap map = new HashMap<>(); map.put(fieldName, errorStr); // return "参数类型(格式)错误:" + fieldName + ": " + errorStr; return new ResultVO(ResultCodeEnum.IllEGAL_PARAMETER, map); } /** *

"BindException" 错误通常是以下原因引起的: *

    *
  • 绑定参数错误:如果您的绑定参数错误,则可能会出现此错误。在这种情况下,您需要检查您的绑定参数并确保它们正确。
  • *
  • 绑定参数类型不正确:如果传入的参数类型不正确,则可能会出现此错误
  • *
*

参考方法 {@link com.su.demo.controller.param_validate.EntityValidateController#test2_validated(User1, HttpServletRequest)} */ @ExceptionHandler(org.springframework.validation.BindException.class) @ResponseStatus(HttpStatus.BAD_REQUEST) public ResultVO bindingException(BindException e) { BindingResult bindingResult = e.getBindingResult(); HashMap map = new HashMap<>(); bindingResult.getFieldErrors().stream() .forEach(fieldError -> map.put(fieldError.getField(), fieldError.getDefaultMessage())); return new ResultVO<>(ResultCodeEnum.IllEGAL_PARAMETER, map); } }

注意,在上面的每个入参校验失败时抛出异常的@ExceptionHandler中,我都加上了@ResponseStatus(HttpStatus.BAD_REQUEST),加上这个之后,返回的状态码就不是200了,而是400 Bad Request。

这个并不强制,可自行决定是否加上这个。


接下来进入正文

1. 方式1:在方法形参的位置,把每个参数平铺开来

这种接口定义方式的特点,就是在在方法形参的位置,把每个参数平铺开来。

兼容的请求方式:GET POST PUT DELETE

兼容的调用方式包括:

  1. query方式传参:即调用方在调用接口时,入参是拼接在接口的URI后面的,如/test_get/requestparam_1?name=wangwu&age=18。这种方式在入参个数比较少的GET请求方式中比较常用。
  2. form-data:即调用方在调用接口时,参数是放在body中的,且Content-type=form-data。
  3. x-www-form-urlencoded:即调用方在调用接口时,参数是放在body中的,且Content-type=x-www-form-urlencoded。

但是无法接收application/json方式传参

1.1 没有校验或仅添加@RequestParam的情形

我们先简单写一个接口,不添加任何的入参校验。

package com.su.demo.controller.param_validate;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.util.Date;
/**
 * 

参数定义方式:直接在方法形参中,写一个个的入参 */ @Validated @RestController @RequestMapping("/param_validate/parameter") public class ParameterValidateController { /** *

接口定义方式:直接在方法形参中,写一个个的入参 *

没有入参校验 *

调用方在调用接口时不传name参数,也可以进入Controller方法,由于没有入参校验,所以没有传name入参也不会报错 *

若不传age,则抛异常{@link org.springframework.web.bind.MissingServletRequestParameterException},可以自行通过{@link ExceptionHandler}进行捕获处理 */ @RequestMapping(path = "/test1_no_validate") public String test1(String name, @RequestParam(value = "age", required = true) Integer age, HttpServletRequest request) { // 从request中获取一些接口请求时的元数据信息,包括请求方式,Content-type等 String requestMetaInfo = String.format("method: [%s], uri: [%s], Content-Type: [%s] ", request.getMethod(), request.getRequestURI(), request.getHeader("Content-Type")); return requestMetaInfo + "===> SUCCESS! hello , name =" + name + ", age = " + age; } }

上面代码中:

1)name字段没有添加任何的校验注解;

2)age字段添加了 @RequestParam注解,当该注解的required 属性设置为true时可以实现简单的必填校验,仅此而已。

1.1.1 用例1

当不传name字段时,调用也没有问题,请求依然可以发送到controller内——这是由于在接口定义处,没有对name字段做任何的校验限制。

SpringBoot接口入参校验的几种方式,在这里插入图片描述,第4张

1.1.2 用例2

当不传age字段时,请求失败,因为接口定义那里,给age字段添加了@RequestParam(required = true)注解

SpringBoot接口入参校验的几种方式,在这里插入图片描述,第5张

1.2 添加@NotNull等校验注解

这种就是把JSR303 相关的注解直接应用到接口参数前面,若某个参数需要有多种校验,可以叠加使用(下面代码有示例)。

同时还需要在 Controller 类上添加@Validated注解,否则校验注解不生效。

推荐只要是想进行入参的校验,那就把@Validated注解添加到你的Controller 类上,防止忘记了。

代码示例:

package com.su.demo.controller.param_validate;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.util.Date;
/**
 * 

参数定义方式:直接在方法形参中,写一个个的入参 */ @Validated @RestController @RequestMapping("/param_validate/parameter") public class ParameterValidateController { /** *

参数定义方式:直接在方法形参中,写一个个的入参 (多个参数+多个校验的示例) *

这种一般就是把 JSR303 相关的注解直接应用到接口参数上即可。同时还需要在 Controller 类上添加{@link Validated} 注解 *

注意:必须同时在Controller类上标注 {@link Validated} 注解,否则约束注解不会生效 *

    *
  • 如果必填的入参在调用时候没有传入,则会抛出 {@link javax.validation.ConstraintViolationException} 异常
  • *
  • 如果调用时候传入的参数格式错误(如给age字段传入aaa这样的非数值),则会抛出 {@link org.springframework.web.method.annotation.MethodArgumentTypeMismatchException} 异常
  • *
*/ @RequestMapping(path = "/test2") public String test2(@NotEmpty(message = "入参name不能为空") String name, @NotNull(message = "入参age不能为空") @Min(value = 10, message = "入参age必须大于10") Integer age, @NotNull(message = "入参birth不能为空") @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") Date birth, HttpServletRequest request) { // 从request中获取一些接口请求时的元数据信息,包括请求方式,Content-type等 String requestMetaInfo = String.format("method: [%s], uri: [%s], Content-Type: [%s] ", request.getMethod(), request.getRequestURI(), request.getHeader("Content-Type")); return requestMetaInfo + "===> SUCCESS! hello2 , name = " + name + ", age = " + age + ", birth = " + birth; } }

在name、age、birth等字段前面,都加上了@NotEmpty @NotNull等校验注解。

如age字段,前面同时添加了@NotNull和@Min校验,实现多种校验效果.

1.2.1 用例1

必填参数如果不传入,调用失败

SpringBoot接口入参校验的几种方式,在这里插入图片描述,第6张

1.2.2 用例2:传入的age大小不符合范围校验

传入的age大小不符合范围校验,调用失败

SpringBoot接口入参校验的几种方式,在这里插入图片描述,第7张

1.2.3 用例3:传入的age类型错误

传入的age类型错误,调用失败

SpringBoot接口入参校验的几种方式,在这里插入图片描述,第8张


2. 方式2:用占位符的方式把入参封装到请求路径中

接口定义方式:用占位符的方式把入参封装到URL请求路径中,然后再通过@PathVariable注解将URL中的占位符参数绑定到控制器(controller)处理方法的形参中。

调用方式:在调用时,入参直接拼接在接口的URL里面,如/param_validate/path_variable/test1/zhangsan/1,其中的zhangsan和1就是就是参数。这种方式在REST风格的接口中比较常用。

兼容的请求方式:GET POST PUT DELETE

适用场景:从技术上来说,这种传参方式同时适用于GET POST PUT DELETE等多种请求方式。 但是一般入参个数较少,且你开发的是REST风格的接口,那可以考虑用这种方式

示例代码如下:

package com.su.demo.controller.param_validate;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.validation.constraints.Min;
@Validated
@RestController
@RequestMapping("/param_validate/path_variable")
public class PathVariableValidateController {
    /**
     *

PathVariable方式 *

    *
  • 如果调用的时候不传type或id参数,则报404
  • *
  • 如果传入的id不是数值格式,则抛出{@link javax.validation.ConstraintViolationException}的异常
  • *
*/ @RequestMapping(value = "/test1/{type}/{id}") public String test1(@PathVariable("type") String type, @PathVariable("id") @Min(value = 1, message = "用户id必须大于1") Integer id, HttpServletRequest request) { // 从request中获取一些接口请求时的元数据信息,包括请求方式,Content-type等 String requestMetaInfo = String.format("method: [%s], uri: [%s], Content-Type: [%s] ", request.getMethod(), request.getRequestURI(), request.getHeader("Content-Type")); return requestMetaInfo + "===> SUCCESS! type = " + type + ", id = " + id; } }

id字段除了用@PathVariable注解修饰,还用了@Min校验注解

2.1 用例1:传入的id范围错误

SpringBoot接口入参校验的几种方式,在这里插入图片描述,第9张

2.1 用例2:不传type或id

调用的时候,如果不传type或id参数,则报404 NotFound错误

SpringBoot接口入参校验的几种方式,在这里插入图片描述,第10张


3. 方式3:把入参封装到一个实体中

定义方式:在方式形参的位置,直接用一个实体类来接收入参

  • 兼容的请求方式:GET POST PUT DELETE
  • 适用场景:从技术上来说,这种传参方式同时适用于GET POST PUT DELETE等请求方式,但是正常情况下一般用在POST或 PUT 请求中

    3.1 @Validated(或@Valid) + 实体,接收入参,接收入参

    直接在方法形参中,用一个实体接收入参。

    并且注意实体前面没有@RequestBody注解,而是只添加了@Validated注解或@Valid注解。

    兼容的请求方式:GET POST PUT DELETE

    兼容的调用方式包括:

    1. query方式传参:即调用方在调用接口时,入参是拼接在接口的URI后面的,如/param_validate/entity/test2_validated?name=wangwu&age=18。这种方式在入参个数比较少的GET请求方式中比较常用。
    2. form-data:即调用方在调用接口时,参数是放在body中的,且Content-type=form-data。
    3. x-www-form-urlencoded:即调用方在调用接口时,参数是放在body中的,且Content-type=x-www-form-urlencoded。

    但是不能接收application/json方式传参(因为实体前面没有@RequestBody注解)

    代码示例:

    package com.su.demo.controller.param_validate;
    import com.su.demo.bean.dto.*;
    import com.su.demo.validate.CompanyEmail;
    import org.springframework.validation.BindingResult;
    import org.springframework.validation.annotation.Validated;
    import org.springframework.web.bind.annotation.*;
    import javax.servlet.http.HttpServletRequest;
    import javax.validation.Valid;
    @Validated
    @RestController
    @RequestMapping("/param_validate/entity")
    public class EntityValidateController {
        /**
         *

    参数定义方式:直接在方法形参中,用一个实体接收入参 *

    注意:一定要在方法的形参位置添加{@link Validated}注解 *

    也可以使用javax提供的(标准JSR-303规范){@link Valid}注解,效果是一样的,参考下面的 {@link EntityValidateController#test3_valid(User1, HttpServletRequest)} 接口 *

      *
    • 如果调用时必填参数没传,或者传入的age字段不是数值格式,则报{@link org.springframework.validation.BindException}异常
    • *
    */ @RequestMapping(value = "/test2_validated") public String test2_validated(@Validated User1 user, HttpServletRequest request) { // 从request中获取一些接口请求时的元数据信息,包括请求方式,Content-type等 String requestMetaInfo = String.format("method: [%s], uri: [%s], Content-Type: [%s] ", request.getMethod(), request.getRequestURI(), request.getHeader("Content-Type")); return requestMetaInfo + " SUCCESS! " + user.toString(); }

    注意,在实体User1 前面,添加了@Validated注解,同样的也可以使用@Valid注解,都可以实现入参校验(因为在此示例中还没有涉及到嵌套校验或分组的功能,所以用@Validated或@Valid都一样)

    其中User1代码如下:

    package com.su.demo.bean.dto;
    import lombok.Data;
    import org.springframework.format.annotation.DateTimeFormat;
    import javax.validation.constraints.*;
    import java.util.Date;
    /**
     * 简单bean对象,结合校验注解使用
     */
    @Data
    public class User1 {
        @NotBlank(message = "姓名不能为空")
        private String name;
        @Digits(message = "年龄必须是数值", integer = 120, fraction = 0)
        @NotNull(message = "年龄不能为空")
        @Min(value = 0, message = "年龄不能小于0")
        @Max(value = 120, message = "年龄不能大于120")
        private Integer age;
        @NotBlank(message = "邮箱不能为空")
        @Email(message = "邮箱不符合邮箱格式")
        private String email;
        @NotBlank(message = "手机号不能为空")
        @Pattern(regexp = "^1[3456789]\\d{9}$", message = "手机号格式错误")
        private String phone;
        @NotBlank(message = "备注不能为空")
        @Size(min = 2, max = 6, message = "备注长度必须在2~6之间")
        private String remark;
        @NotNull(message = "生日不能为空")
        @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
        private Date birth;
    }
    

    注意,在实体User1 里面,每个属性都可以添加相应的(1个或多个)校验注解

    3.1.1 用例1:

    调用时必填入参如果没填,调用失败

    SpringBoot接口入参校验的几种方式,在这里插入图片描述,第11张

    3.1.2 用例2:

    调用时入参格式错误,调用失败

    SpringBoot接口入参校验的几种方式,在这里插入图片描述,第12张

    3.2 @RequestBody + @Validated(或@Valid) + 实体,接收入参

    直接在方法形参中,用一个实体接收入参。

    并且实体前面添加了@RequestBody注解,以及@Validated(或@Valid)注解。

    兼容的请求方式:GET POST PUT DELETE

    兼容的调用方式:

    1. application/json:即调用方在调用接口时,参数是放在body中的,且Content-type=application/json

    但是不能接收query方式、form-data、x-www-form-urlencoded等方式传参,只能接收application/json的入参(因为在实体前面添加了@ResponseBody注解)

    代码示例:

    package com.su.demo.controller.param_validate;
    import com.su.demo.bean.dto.*;
    import com.su.demo.validate.CompanyEmail;
    import org.springframework.validation.BindingResult;
    import org.springframework.validation.annotation.Validated;
    import org.springframework.web.bind.annotation.*;
    import javax.servlet.http.HttpServletRequest;
    import javax.validation.Valid;
    @Validated
    @RestController
    @RequestMapping("/param_validate/entity")
    public class EntityValidateController {
        /**
         *

    参数定义方式:直接在方法形参中,用一个实体接收入参 *

    加上{@link RequestBody}之后,调用方在调用该方法的时候,就只能通过body来传参了 *

    注意:在方法的形参位置添加{@link Valid}注解。且跟上一个方法相比,该方法多了@RequestBody注解,我们尝试通过将参数放在requestBody中,看看会不会进行入参校验 *

    也可以使用 {@link Validated}注解,效果是一样的,参考上面的 {@link EntityValidateController#test2_validated_requestbody(User11, HttpServletRequest)} 接口 *

      *
    • 如果调用时必填参数没传,或传入的手机号格式错误,则报{@link org.springframework.web.bind.MethodArgumentNotValidException}异常
    • *
    • 如果调用时传入的birth日期格式错误,则报{@link org.springframework.http.converter.HttpMessageNotReadableException}异常
    • *
    */ @RequestMapping(value = "/test3_valid_requestbody") public String test3_valid_requestbody(@RequestBody @Valid User11 user, HttpServletRequest request) { // 从request中获取一些接口请求时的元数据信息,包括请求方式,Content-type等 String requestMetaInfo = String.format("method: [%s], uri: [%s], Content-Type: [%s] ", request.getMethod(), request.getRequestURI(), request.getHeader("Content-Type")); return requestMetaInfo + "===> SUCCESS!" + user.toString(); } }

    注意,在实体User11 前面,添加了@RequestBody注解;

    并且本次代码示例使用了@Valid注解,当然也可以使用@Validated注解,在本次代码示例中它两实现的入参校验效果是一样的(因为在此示例中还没有涉及到嵌套校验或分组的功能,所以用@Validated或@Valid都一样)。

    其中User11代码如下:

    package com.su.demo.bean.dto;
    import com.fasterxml.jackson.annotation.JsonFormat;
    import lombok.Data;
    import org.springframework.format.annotation.DateTimeFormat;
    import javax.servlet.http.HttpServletRequest;
    import javax.validation.constraints.*;
    import java.util.Date;
    /**
     * 简单bean对象
     */
    @Data
    public class User11 {
        @NotBlank(message = "姓名不能为空")
        private String name;
        @Digits(message = "年龄必须是数值", integer = 120, fraction = 0)
        @NotNull(message = "年龄不能为空")
        @Min(value = 0, message = "年龄不能小于0")
        @Max(value = 120, message = "年龄不能大于120")
        private Integer age;
        @NotBlank(message = "邮箱不能为空")
        @Email(message = "邮箱不符合邮箱格式")
        private String email;
        @NotBlank(message = "手机号不能为空")
        @Pattern(regexp = "^1[3456789]\\d{9}$", message = "手机号格式错误")
        private String phone;
        @NotBlank(message = "备注不能为空")
        @Size(min = 2, max = 6, message = "备注长度必须在2~6之间")
        private String remark;
        // todo 这里如果用 @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss"),则无法解决@RequestBody的Date入参格式化问题
        /**
         *

    如果使用{@link JsonFormat}注解,则可以解决{@link com.su.demo.controller.param_validate.EntityValidateController#test2_validated_requestbody(User11)}的Date入参格式化问题 */ @NotNull(message = "生日不能为空") @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") private Date birth; }

    3.2.1 用例1:入参为空

    调用时若必填入参没有传入,调用失败

    SpringBoot接口入参校验的几种方式,在这里插入图片描述,第13张

    注意请求入参是放在body当中的,并且Content-type=application/json

    3.2.2 用例2:入参格式错误

    调用时若入参格式错误,调用失败

    SpringBoot接口入参校验的几种方式,在这里插入图片描述,第14张

    3.3(嵌套校验) @RequestBody + @Validated(或@Valid) + 实体,接收入参

    直接在方法形参中,用一个实体接收入参。

    并且实体前面添加了@RequestBody注解,以及@Validated(或@Valid)注解。并且,这次加上了入参的嵌套校验

    所谓的嵌套验证,就比如我们有3个对象,分别是User2、Dept和Hobby,其中,User2对象内,有两个属性,分别就是Dept和Hobby,而嵌套验证要求在接口接收入参的时候,除了对User2对象内的常规属性(如name、age等)进行@NotNull等的校验,对Dept和Hobby内的属性也同样进行@NotNull等的校验(关于User2、Dept和Hobby这3个对象,下面代码有示例)。

    兼容的调用方式:

    1. application/json:即调用方在调用接口时,参数是放在body中的,且Content-type=application/json

    但是不能接收query方式、form-data、x-www-form-urlencoded待方式传参,只能接收application/json的入参(因为在实体前面添加了@ResponseBody注解)

    代码示例:

    package com.su.demo.controller.param_validate;
    import com.su.demo.bean.dto.*;
    import com.su.demo.validate.CompanyEmail;
    import org.springframework.validation.BindingResult;
    import org.springframework.validation.annotation.Validated;
    import org.springframework.web.bind.annotation.*;
    import javax.servlet.http.HttpServletRequest;
    import javax.validation.Valid;
    @Validated
    @RestController
    @RequestMapping("/param_validate/entity")
    public class EntityValidateController {
       /**
         * requestBody方式
         * 测试【嵌套参数】的校验
         * 
      *
    • 如果调用时嵌套的必填参数没传,则报{@link org.springframework.web.bind.MethodArgumentNotValidException}异常
    • *
    */ @RequestMapping(value = "/test4_validated_requestbody_nest") public String test4_validated_requestbody_nest(@RequestBody @Validated User2 user2, HttpServletRequest request) { // 从request中获取一些接口请求时的元数据信息,包括请求方式,Content-type等 String requestMetaInfo = String.format("method: [%s], uri: [%s], Content-Type: [%s] ", request.getMethod(), request.getRequestURI(), request.getHeader("Content-Type")); return requestMetaInfo + "===> SUCCESS!" + user2.toString(); } }

    上面代码中,User2前面可以用@Validated也可以用@Valid,效果一样(因为在这个地方,还没有涉及嵌套)

    其中User2、Dept和Hobby这3个对象的代码如下:

    package com.su.demo.bean.dto;
    import lombok.Data;
    import org.hibernate.validator.constraints.Length;
    import javax.validation.Valid;
    import javax.validation.constraints.*;
    import java.util.List;
    /**
     * 嵌套对象
     */
    @Data
    public class User2 {
        @NotBlank(message = "姓名不能为空")
        private String name;
        @Min(value = 0, message = "年龄不能小于0")
        @Max(value = 120, message = "年龄不能大于120")
        private Integer age;
        @Email(message = "邮箱不符合邮箱格式")
        private String email;
        @Pattern(regexp = "^1[3456789]\\d{9}$", message = "手机号格式错误")
        private String phone;
        @NotBlank(message = "备注不能为空")
        @Size(min = 2, max = 6, message = "备注长度必须在2~6之间")
        private String remark;
        @Valid /** 注意,这里不能使用@Validated!!!*/
        @NotNull(message = "部门不能为null")
        private Dept dept;
        @Valid
        @NotEmpty(message = "爱好不能为空")
        @Size(min = 2, message = "至少填写2个爱好")
        private List hobbyList;
    }
    @Data
    class Dept {
        private Long id;
        @NotBlank(message = "入参 部门名称deptName 不能为空")
        private String deptName;
    }
    @Data
    class Hobby {
        private Long id;
        @NotBlank(message = "爱好名称不能为空")
        @Length(min = 2, max = 15, message = "爱好名称的长度应该在2~15之间")
        private String name;
    }
    

    注意,上面User2类内的dept和hobbyList属性上面,必须使用@Valid注解,不能用@Validated,因为@Valid有嵌套验证的功能,而@Validated没有。

    3.3.1 用例1

    调用时若嵌套对象的属性不传,则调用失败

    SpringBoot接口入参校验的几种方式,在这里插入图片描述,第15张

    3.3.2 用例2

    嵌套入参正确,调用成功

    SpringBoot接口入参校验的几种方式,在这里插入图片描述,第16张

    3.4(分组校验) @RequestBody + @Validated + 实体,接收入参

    @Validated 还提供了分组功能,可以在参数验证时,根据不同的分组采用不同的验证机制。

    比如有如下的User3的类:

    @Data
    public class User3 {
        /**
         * 自增主键id
         */
        @Null(message = "用户id不能传值", groups = OnSave.class)
        @NotNull(message = "用户id不能为空", groups = OnUpdate.class)
        private Long id;
        @NotBlank(message = "姓名不能为空", groups = {OnSave.class, OnUpdate.class})
        private String name;
        @NotNull(message = "备注不能为空", groups = OnSave.class)
        private String remark;
        /**
         * 分组
         */
        public interface OnSave {}
        public interface OnUpdate {}
    }
    

    (举个例子)我们在业务上可能会有这样的一个处理逻辑:

    1. 当User3对象用来接收新建保存接口的数据时,要求前端传过来的用户数据中不能有id字段的数据,因为id字段是自增主键,在新建用户的时候,应该是由数据库自己生成的,而不是前端传过来
    2. 而当User3对象用来接收修改保存接口的数据时,就要求前端传过来的用户数据中必须有id字段了,因为后端需要根据id去做update操作

      那为了满足上面的需求,我们在定义User3对象的时候,就可以使用上@Validated 提供的分组功能了。

    如上面代码所示,可以在id字段的@NotNull注解中,设置它的groups属性:@NotNull(message = "用户id不能为空", groups = OnUpdate.class),这个就表示只有当groups = OnUpdate.class时,才会应用该@NotNull校验注解。

    同样的也可以自行去关注上面其他字段的分组设置,如remark字段的@NotNull注解使用的是groups = OnSave.class分组。

    在定义好上面的bean对象之后,我们在定义接口时,做如下操作:

    • 在定义保存接口的时候,使用OnSave分组的校验规则:@Validated(User3.OnSave.class)
    • 在定义修改接口的时候,使用OnUpdate分组的校验规则:@Validated(User3.OnUpdate.class)

      示例代码如下:

      package com.su.demo.controller.param_validate;
      import com.su.demo.bean.dto.*;
      import com.su.demo.validate.CompanyEmail;
      import org.springframework.validation.BindingResult;
      import org.springframework.validation.annotation.Validated;
      import org.springframework.web.bind.annotation.*;
      import javax.servlet.http.HttpServletRequest;
      import javax.validation.Valid;
      @Validated
      @RestController
      @RequestMapping("/param_validate/entity")
      public class EntityValidateController {
          /**
           * 在实际项目中,可能多个方法需要使用同一个DTO类来接收参数,
           * 而不同方法的校验规则很可能是不一样的。这个时候,简单地在DTO类的字段上加约束注解无法解决这个问题。
           * 因此,spring-validation支持了分组校验的功能,专门用来解决这类问题。还是上面的例子,比如保存User的时候,
           * UserId是可空的,但是更新User的时候,UserId的值必须>=10000000000000000L;其它字段的校验规则在两种情况下一样。
           */
          /**
           * 校验分组 OnSave
           * save时,校验的规则是OnSave,要求必须填写name和remark,但是id可以为空
           */
          @RequestMapping(value = "test6_group_save")
          public String test6_group_save(@RequestBody @Validated(User3.OnSave.class) User3 user3, HttpServletRequest request) {
              // 从request中获取一些接口请求时的元数据信息,包括请求方式,Content-type等
              String requestMetaInfo = String.format("method: [%s], uri: [%s], Content-Type: [%s] ", request.getMethod(), request.getRequestURI(),  request.getHeader("Content-Type"));
              System.out.println("CheckParamController.test_e1_save ---> " + user3);
              return requestMetaInfo + "===> SUCCESS! ---> " + user3;
          }
         /**
           * 校验分组 OnUpdate
           * update时,校验的规则是OnUpdate,要求必须填写id和name,但是remark可以为空
           */
          @RequestMapping(value = "test6_group_update")
          public String test6_group_update(@RequestBody @Validated(User3.OnUpdate.class) User3 user3, HttpServletRequest request) {
              // 从request中获取一些接口请求时的元数据信息,包括请求方式,Content-type等
              String requestMetaInfo = String.format("method: [%s], uri: [%s], Content-Type: [%s] ", request.getMethod(), request.getRequestURI(),  request.getHeader("Content-Type"));
              System.out.println("CheckParamController.test_e1_update ---> " + user3);
              return requestMetaInfo + "===> SUCCESS! ---> " + user3;
          }
       }
      

      上面提供了两个接口:

      第1个接口是保存接口,使用OnSave分组:@Validated(User3.OnSave.class);

      第2个接口是修改接口,使用OnUpdate分组:@Validated(User3.OnUpdate.class);

      并且注意,此时上面接口定义时,就只能使用@Validated了,不能使用@Valid,因为@Valid不提供分组的功能!

      3.4.1 用例1:测试OnSave分组

      调用接口时如果传了id字段,或者没有传remark字段, 则调用失败

      SpringBoot接口入参校验的几种方式,在这里插入图片描述,第17张

      3.4.2 用例2:测试OnUpdate分组

      调用接口时如果不传id字段, 则调用失败

      SpringBoot接口入参校验的几种方式,在这里插入图片描述,第18张

      3.5(自定义校验) @RequestBody + @Validated(或@Valid) + 实体,接收入参

      有时候,如果内置的校验注解不能满足我们的需求,那我们可以自行定义校验用的注解,作为示例,下面我将自定义一个用于校验指定邮箱格式的注解,要求邮箱格式必须是QQ邮箱格式:

      先定义一个名为CompanyEmail的注解:

      package com.su.demo.validate;
      import javax.validation.Constraint;
      import javax.validation.Payload;
      import java.lang.annotation.Documented;
      import java.lang.annotation.Retention;
      import java.lang.annotation.Target;
      import static java.lang.annotation.ElementType.*;
      import static java.lang.annotation.ElementType.TYPE_USE;
      import static java.lang.annotation.RetentionPolicy.RUNTIME;
      /**
       * 自定义的【QQ邮箱】格式校验的注解
       */
      @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
      @Retention(RUNTIME)
      @Documented
      @Constraint(validatedBy = {CompanyEmailValidator.class})
      public @interface CompanyEmail {
          String message() default "必须是QQ邮箱,如123456@qq.com";
          Class[] groups() default { };
          Class[] payload() default { };
      }
      

      注意上面代码,跟普通的注解不一样的是,我们的这个注解中还通过@Constraint(validatedBy = {CompanyEmailValidator.class}),去绑定它对应的验证器为CompanyEmailValidator,验证器CompanyEmailValidator中就定义了具体的验证规则!

      其中CompanyEmailValidator的代码如下:

      package com.su.demo.validate;
      import org.springframework.util.StringUtils;
      import javax.validation.ConstraintValidator;
      import javax.validation.ConstraintValidatorContext;
      /**
       * 自定义的QQ邮箱的的验证器
       */
      public class CompanyEmailValidator implements ConstraintValidator {
          @Override
          public boolean isValid(String value, ConstraintValidatorContext context) {
          	// 超简单的验证,如果value不为空且以@qq.com结尾,则返回true
              return null != value && value.endsWith("@qq.com");
          }
      }
      

      接下来,在接口中使用该自定义的校验注解:

      package com.su.demo.controller.param_validate;
      import com.su.demo.bean.dto.*;
      import com.su.demo.validate.CompanyEmail;
      import org.springframework.validation.BindingResult;
      import org.springframework.validation.annotation.Validated;
      import org.springframework.web.bind.annotation.*;
      import javax.servlet.http.HttpServletRequest;
      import javax.validation.Valid;
      @Validated
      @RestController
      @RequestMapping("/param_validate/entity")
      public class EntityValidateController {
          /**
           *

      使用自定义的校验 *

      必须在Controller上添加{@link Validated}注解,否则自定义的校验不起作用 */ @RequestMapping(value = "/test7_custom_validator") public String test7_custom_custom_validator(@CompanyEmail String email, HttpServletRequest request) { // 从request中获取一些接口请求时的元数据信息,包括请求方式,Content-type等 String requestMetaInfo = String.format("method: [%s], uri: [%s], Content-Type: [%s] ", request.getMethod(), request.getRequestURI(), request.getHeader("Content-Type")); return requestMetaInfo + "===> SUCCESS! email = " + email; } }

      在入参email前面加上了自定义的@CompanyEmail注解

      3.5.1 用例

      email入参不是QQ邮箱,调用失败

      SpringBoot接口入参校验的几种方式,在这里插入图片描述,第19张

      3.6快速失败校验配置

      在上面的代码示例中,可以看到会校验所有字段,并将所有不符合校验规则的字段检测出来。这种校验策略是实体类参数校验的普通校验模式(默认的校验模式)。

      SpringBoot接口入参校验的几种方式,在这里插入图片描述,第20张

      实际上有两种校验模式:

      1. 普通模式(默认是这个模式): 会校验完所有的属性,然后返回所有的验证失败信息
      2. 快速失败模式:只要有一个验证失败,则返回。

        如果想要配置第二种模式快速失败模式,需要添加如下配置类

      package com.su.demo.config;
      import org.hibernate.validator.HibernateValidator;
      import org.springframework.context.annotation.Bean;
      import org.springframework.context.annotation.Configuration;
      import javax.validation.Validation;
      import javax.validation.Validator;
      import javax.validation.ValidatorFactory;
      /**
       * 

      在上面实体类参数校验的例子中,可以看到会校验所有字段,并将所有不符合校验规则的字段打印出来。这种校验策略是实体类参数校验的普通校验模式(默认的校验模式)。实际上有两种校验模式: *

      ①普通模式(默认是这个模式): 会校验完所有的属性,然后返回所有的验证失败信息 *

      ②快速失败模式: 只要有一个验证失败,则返回。 * *

      如果想要配置第二种模式,需要添加如下配置类 */ @Configuration public class ValidateConfig { @Bean public Validator validator() { ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class) .configure() .failFast( true ) // 快速失败模式: 只要有一个验证失败,则返回 .buildValidatorFactory(); Validator validator = validatorFactory.getValidator(); return validator; } }

      添加这个配置类之后,重启项目,此时即便入参中有多个入参都不符合校验规则,当程序检测到第一个验证失败,就立即返回了,不再继续检测其他字段了:

      SpringBoot接口入参校验的几种方式,在这里插入图片描述,第21张

      快速失败模式可以根据自己项目的实际需求,决定是否配置


      4. 方式4:用原生的HttpServletRequest接收参数

      用原生的HttpServletRequest,既可以接收query入参,也可以接收body入参。

      但是这种用原生的HttpServletRequest来接收入参方式,没有办法结合@NotNull等校验注解,对入参进行校验。

      如果一定想要进行入参的校验,那就只能在代码中手动的进行判断了。

      因此,一般都很少用这种方式来接收入参了。

      package com.su.demo.controller.param_validate;
      import cn.hutool.core.io.IoUtil;
      import com.alibaba.fastjson.JSON;
      import com.su.demo.bean.dto.*;
      import com.su.demo.bean.vo.base.ResultCodeEnum;
      import com.su.demo.bean.vo.base.ResultVO;
      import com.su.demo.validate.CompanyEmail;
      import org.springframework.validation.BindingResult;
      import org.springframework.validation.annotation.Validated;
      import org.springframework.web.bind.annotation.*;
      import javax.servlet.ServletInputStream;
      import javax.servlet.http.HttpServletRequest;
      import javax.validation.Valid;
      import java.io.IOException;
      @Validated
      @RestController
      @RequestMapping("/param_validate/entity")
      public class EntityValidateController {
          /**
           * 
        *
      • 入参类别:body参数方式 *
      • 定义方式: 在方式形参的位置,用原生的{@link HttpServletRequest}接收 *
      • 调用方式: 参数放在请求体 body 当中 *
      • 兼容的请求方式:GET POST PUT DELETE
      • *
      • 适用场景:从技术上来说,这种传参方式同时适用于GET POST PUT DELETE等请求方式,但是正常情况下比较少用这种方式来获取body参数了
      • *
      • 优点:{@link HttpServletRequest}是整个请求,可以获取到所有的数据.且HttpServletRequest、HttpServletResponse都是内置对象,可以使用
      • *
      • 缺点:代码中再去从request中拿到参数,老麻烦,并且还没有办法直接将校验的注解应用过来
      • *
      */ @RequestMapping(value = "/test10_request") public ResultVO test10(HttpServletRequest request) throws IOException { // 从request中获取一些接口请求时的元数据信息,包括请求方式,Content-type等 String requestMetaInfo = String.format("method: [%s], uri: [%s], Content-Type: [%s] ", request.getMethod(), request.getRequestURI(), request.getHeader("Content-Type")); ServletInputStream inputStream = request.getInputStream(); // 用hutool工具包的read方式,将inputStream读取成string String body = IoUtil.read(inputStream, "UTF-8"); System.out.println("body = " + body); // 用fastjson将json字符串转换成bean User1 user = JSON.parseObject(body, User1.class); //虽然User1的属性上加了@NotNull等注解,但是并不起作用 // 只能在代码中手动校验 // if (null == user.getName()) { // return new ResultVO(ResultCodeEnum.IllEGAL_PARAMETER, "入参name不能为空"); // } return new ResultVO(requestMetaInfo + " ---> SUCCESS! user = " + user); } }

      上面代码示例是用原生的HttpServletRequest来接收body中的application/json入参,其实HttpServletRequest同样也可以用来接收query入参,只需要在代码中通过request.getParameter("参数名称")的方式来获取入参即可。

      为了节约篇幅,这里就不再举例了。

      用例:

      SpringBoot接口入参校验的几种方式,在这里插入图片描述,第22张


      5. 方式6:用String结合@RequestBody来接收body入参

      这种方式的示例代码如下,它跟上面那种方式一样的缺点——没有办法结合@NotNull等校验注解,对入参进行校验。

      因此,一般也很少用这种方式来接收入参。

          /**
           * 直接用一个String接收body参数
           */
          @RequestMapping(value = "/test11_str")
          public ResultVO test11(@RequestBody String body, HttpServletRequest request) throws IOException {
              // 从request中获取一些接口请求时的元数据信息,包括请求方式,Content-type等
              String requestMetaInfo = String.format("method: [%s], uri: [%s], Content-Type: [%s] ", request.getMethod(), request.getRequestURI(),  request.getHeader("Content-Type"));
              System.out.println("body = " + body);
              // 用fastjson将json字符串转换成bean
              User1 user = JSON.parseObject(body, User1.class); //虽然User1的属性上加了@NotNull等注解,但是并不起作用
              // 只能在代码中手动校验
      //        if (null == user.getName()) {
      //            return new ResultVO(ResultCodeEnum.IllEGAL_PARAMETER, "入参name不能为空");
      //        }
              return new ResultVO(requestMetaInfo + " ---> SUCCESS! user = " + user);
          }
      

      这种方式,其实就是直接以String格式,拿到body的内容,然后再自行将body中的json串转换成对应的bean对象

      调用用例:

      SpringBoot接口入参校验的几种方式,在这里插入图片描述,第23张


      6. 方式5:用Map来接收RequestBody入参

      这种方式的示例代码如下,它跟上面那种方式一样的缺点——没有办法结合@NotNull等校验注解,对入参进行校验。

      因此,同样也很少用这种方式来接收入参。

      但是如果你的接口入参个数多,你又懒得定义对应的实例来接收入参,并且也不想对入参进行校验,那你图方便的话,也可以选择使用这种方式来接收入参。自行决定。

          /**
           * 直接用一个Map接收body参数
           */
          @RequestMapping(value = "/test12_map")
          public ResultVO test12(@RequestBody Map bodyMap, HttpServletRequest request) throws IOException {
              // 从request中获取一些接口请求时的元数据信息,包括请求方式,Content-type等
              String requestMetaInfo = String.format("method: [%s], uri: [%s], Content-Type: [%s] ", request.getMethod(), request.getRequestURI(),  request.getHeader("Content-Type"));
              // 只能在代码中手动校验
              if (null == bodyMap.get("name")) {
                  return new ResultVO(ResultCodeEnum.IllEGAL_PARAMETER, "入参name不能为空");
              }
              return new ResultVO(requestMetaInfo + " ---> SUCCESS! bodyMap = " + bodyMap);
          }
      

      调用用例:

      SpringBoot接口入参校验的几种方式,在这里插入图片描述,第24张


      结束。