0


SpringBoot接口入参校验的几种方式

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

0. 前言

日常业务开发中,有时不可避免的需要进行请求参数的校验,对于一些

与业务无关

的、简单的参数校验(如非空校验、长度限制等),则可以采用javax.validation 和 hibernate-validator通过

注解

的方式实现校验。

web开发中,

前端

的参数校验是为了

用户体验

,而

后端

的参数校验更多是为了

安全

,防止非法参数对业务造成影响。
此外,入参校验其实也是后端

代码规范

的一个重要体现。

1)@Validated和@Valid的区别

SpringBoot中常用的校验注解是@Validated和@Valid,@Validated是对@Valid进行了二次封装,它们的区别如下
不同@Valid@Validated来源Hibernate validation 提供的校验注解Spring Validator 提供的校验注解,是 Hibernate validation 基础上的

增强版

注解位置可用在构造函数、方法、方法参数 和 成员属性上用在 类、方法和方法参数上。但

不能用于成员属性

嵌套验证可用在

级联对象

成员属性

上面(后面会有使用示例)不支持分组无此功能提供

分组

功能,可以在入参验证时,根据不同的分组采用不同的验证机制
可以看到,

@Validated

@Valid

两者功能上最大的不同就是嵌套验证分组的功能,除此之外,它们的功能差不多。因此在下面的代码示例中,将发现有些地方用

@Validated

@Valid

,实现的入参校验效果是一样的。

2)Hibernate validation与Spring 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

,则需要手动引入依赖。

<!--        jsr 303--><dependency><groupId>javax.validation</groupId><artifactId>validation-api</artifactId><version>2.0.1.Final</version></dependency><!--         hibernate validator--><dependency><groupId>org.hibernate.validator</groupId><artifactId>hibernate-validator</artifactId><version>6.2.0.Final</version></dependency>

或者,直接在

pom.xml

中引入springboot的

starter场景启动器

(推荐这种方式):

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId></dependency>

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

在接口定义时如果加入了入参校验,那么当调用方在调用接口时如果传入的参数不符合校验规则,一般来说后端代码是会**抛出相应的

异常

的**,并且返回给到调用方的结果也会不太友好,如下所示:
在这里插入图片描述
从后端代码的控制台,可以看到抛出了相应的

异常

,下图是示例:
在这里插入图片描述

如果你希望你的接口无论如何都返回一个

友好的返回结果格式

,如下所示:
在这里插入图片描述
想要实现这样的效果,可以利用

@ControllerAdvice
  • @ExceptionHandler
    
    的方式,捕获对应的异常,然后在捕获异常的方法内返回相应的ResultVO对象。

下面是我利用

@ControllerAdvice
  • @ExceptionHandler
    
    ,做的关于入参校验的异常捕获处理,仅供参考:
packagecom.su.demo.exception;importcom.su.demo.bean.dto.*;importcom.su.demo.bean.vo.base.ResultCodeEnum;importcom.su.demo.bean.vo.base.ResultVO;importcom.su.demo.controller.param_validate.ParameterValidateController;importcom.su.demo.controller.param_validate.CheckParamController2;importorg.springframework.http.HttpStatus;importorg.springframework.http.converter.HttpMessageNotReadableException;importorg.springframework.validation.BindException;importorg.springframework.validation.BindingResult;importorg.springframework.web.HttpRequestMethodNotSupportedException;importorg.springframework.web.bind.MissingServletRequestParameterException;importorg.springframework.web.bind.annotation.*;importorg.springframework.web.method.annotation.MethodArgumentTypeMismatchException;importjavax.servlet.http.HttpServletRequest;importjavax.validation.ConstraintViolation;importjava.util.Arrays;importjava.util.Date;importjava.util.HashMap;importjava.util.Map;importjava.util.stream.Collectors;/**
 * <p>用统一异常处理来返回一个更友好的提示
 * <p>比如我们系统要求无论发送什么异常,http的状态码必须返回200,由业务码去区分系统的异常情况。
 *
 * <p>当参数校验异常的时候,该统一异常处理类在控制台打印信息的同时把bad request的字符串和HttpStatus.BAD_REQUEST所表示的状态码400返回给调用方(用@ResponseBody注解实现,表示该方法的返回结果直接写入HTTP response body 中)。其中:
 * <ul>
 *     <li>{@link ControllerAdvice}控制器增强,使@ExceptionHandler、@InitBinder、@ModelAttribute注解的方法应用到所有的 @RequestMapping注解的方法。</li>
 *     <li>{@link ExceptionHandler}异常处理器,此注解的作用是当出现其定义的异常时进行处理的方法,此例中处理ValidationException异常。</li>
 * </ul>
 *
 *
 */@RestControllerAdvicepublicclassGlobalControllerExceptionHandler{// 参数校验异常1/**
     * <p>该ExceptionHandler对应的情况:接口定义处添加了如{@link javax.validation.constraints.NotNull}等校验注解,但是调用接口时却没有传参
     * <p>此时接口会报{@link javax.validation.ConstraintViolationException}异常且返回如下的
     * <b>不友好的结果</b> 给接口调用方
     * <p>{
     * <p>    "timestamp": "2024-01-31T06:07:15.183+00:00",
     * <p>    "status": 500,
     * <p>    "error": "Internal Server Error",
     * <p>    "path": "/param_validate/parameter/test2"
     * <p>}
     * <p> 参考接口:{@link ParameterValidateController#test2(String, Integer, Date, HttpServletRequest)}
     */@ExceptionHandler(javax.validation.ConstraintViolationException.class)@ResponseStatus(HttpStatus.BAD_REQUEST)// 加上这个之后,返回的状态码就不是200了,而是400 Bad RequestpublicResultVOconstrainViolationException(javax.validation.ConstraintViolationException e){// return e.getMessage(); // 这种方式只返回第一个错误(其实也可以)// 下面这种方式,可以返回所有字段的查检不通过的结果String res = e.getConstraintViolations().stream().map(ConstraintViolation::getMessage).collect(Collectors.joining(";"));returnnewResultVO(ResultCodeEnum.IllEGAL_PARAMETER, res);}/**
     * <p>该ExceptionHandler对应的情况,当如下接口定义中必填参数不传或者传入的参数格式错误时,报当前exception
     * <p>参考接口:{@link com.su.demo.controller.param_validate.EntityValidateController#test2_validated_requestbody(User11, HttpServletRequest)}
     */@ExceptionHandler(org.springframework.web.bind.MethodArgumentNotValidException.class)@ResponseStatus(HttpStatus.BAD_REQUEST)publicResultVOmethodArgNotValidException(org.springframework.web.bind.MethodArgumentNotValidException e){BindingResult bindingResult = e.getBindingResult();if(!bindingResult.hasErrors()){
            e.printStackTrace();returnnewResultVO<>(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("; "));returnnewResultVO<>(ResultCodeEnum.IllEGAL_PARAMETER, res2);}/**
     * <p>该ExceptionHandler对应的情况:调用接口时请求类型错误
     * <p>此时如果不进行对应的异常处理,则会报{@link org.springframework.web.HttpRequestMethodNotSupportedException}异常且返回如下的
     * <b>不友好的结果</b> 给接口调用方
     * <p>{
     * <p>    "timestamp": "2024-01-02T09:06:04.511+0000",
     * <p>    "status": 405,
     * <p>    "error": "Method Not Allowed",
     * <p>    "message": "Request method 'POST' not supported",
     * <p>    "path": "/test_response_advice/string"
     * <p>}
     */@ExceptionHandler(HttpRequestMethodNotSupportedException.class)@ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)publicResultVOmethodNotSupportException(HttpRequestMethodNotSupportedException e){returnnewResultVO(ResultCodeEnum.METHOD_NOT_ALLOWED,"接口仅支持如下请求方式: "+Arrays.toString(e.getSupportedMethods()));}/**
     * <p>该ExceptionHandler对应的情况:在调用【入参为格式requestBody】的接口时:
     * <ul>
     *     <li>1) 不传body请求体</li>
     *     <li>2) 或传的body请求体格式错误,如:<b>{ "age": 130, ""}</b></li>
     *     <li>3) 或传的body请求体内的日期格式错误</li>
     * </ul>
     * 此时如果不进行对应的异常处理,则会报{@link org.springframework.http.converter.HttpMessageNotReadableException}异常且返回如下的
     *<p> 参考方法 {@link com.su.demo.controller.param_validate.EntityValidateController#test3_valid_requestbody(User11, HttpServletRequest)}
     */@ExceptionHandler(HttpMessageNotReadableException.class)@ResponseStatus(HttpStatus.BAD_REQUEST)publicResultVOhttpMsgNotReadableExp(HttpMessageNotReadableException e){returnnewResultVO(ResultCodeEnum.IllEGAL_PARAMETER, e.getMessage());}/**
     * <p>该ExceptionHandler对应的情况:在调用【入参设置为如(@RequestParam("name") String name)】的接口时 ,入参name却不传(为null)的时候报的异常
     * <p>因为 {@link RequestParam} 注解的required默认是true,因此被这个注解标记的入参如果为空,
     * 则会报{@link org.springframework.web.bind.MissingServletRequestParameterException}异常
     * <p>如果不做此异常捕获处理,则接口返回如下的 <b>不友好的结果</b> 给调用方:
     *<p>{
     *<p>   "timestamp": "2023-12-29T03:10:57.961+0000",
     *<p>     "status": 400,
     *<p>     "error": "Bad Request",
     *<p>     "message": "Required String parameter 'name' is not present",
     *<p>     "path": "/param_type/get_reqestparam"
     *<p> }
     *<p> 参考方法: {@link ParameterValidateController#test1(String, Integer, HttpServletRequest)}
     */@ExceptionHandler(org.springframework.web.bind.MissingServletRequestParameterException.class)@ResponseStatus(HttpStatus.BAD_REQUEST)publicResultVOmissingRequestParamException(MissingServletRequestParameterException e){
        e.printStackTrace();returnnewResultVO(ResultCodeEnum.IllEGAL_PARAMETER, e.getMessage());}/**
     * <p>该ExceptionHandler对应的是类似如下的情况入参类型错误:
     * <ul>
     *     <li>1) 字段age类型是Integer,但是入参却是test这样的字符串</li>
     *     <li>2) 字段enable类型是Boolean,但是入参却是666这样的数值</li>
     *     <li>3) 字段birth类型是Date且@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss"),但是入参却是<i>20230101</i>这样的错误格式</li>
     *     <li>4) 等等</li>
     * </ul>
     * <p>参考方法: {@link ParameterValidateController#test2(String, Integer, Date, HttpServletRequest)}
     */@ExceptionHandler(org.springframework.web.method.annotation.MethodArgumentTypeMismatchException.class)@ResponseStatus(HttpStatus.BAD_REQUEST)publicResultVOmethodArgTypeMismatchException(MethodArgumentTypeMismatchException e){//        e.printStackTrace();String fieldName = e.getName();String errorStr = e.getCause().toString();HashMap<String,String> map =newHashMap<>();
        map.put(fieldName, errorStr);//        return "参数类型(格式)错误:" + fieldName + ": " + errorStr;returnnewResultVO(ResultCodeEnum.IllEGAL_PARAMETER, map);}/**
     * <p>"BindException" 错误通常是以下原因引起的:
     * <ul>
     * <li>绑定参数错误:如果您的绑定参数错误,则可能会出现此错误。在这种情况下,您需要检查您的绑定参数并确保它们正确。</li>
     * <li>绑定参数类型不正确:如果传入的参数类型不正确,则可能会出现此错误</li>
     * </ul>
     * <p>参考方法 {@link com.su.demo.controller.param_validate.EntityValidateController#test2_validated(User1, HttpServletRequest)}
     */@ExceptionHandler(org.springframework.validation.BindException.class)@ResponseStatus(HttpStatus.BAD_REQUEST)publicResultVO<Map>bindingException(BindException e){BindingResult bindingResult = e.getBindingResult();HashMap<String,String> map =newHashMap<>();
        bindingResult.getFieldErrors().stream().forEach(fieldError -> map.put(fieldError.getField(), fieldError.getDefaultMessage()));returnnewResultVO<>(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

的情形

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

packagecom.su.demo.controller.param_validate;importorg.springframework.format.annotation.DateTimeFormat;importorg.springframework.validation.BindingResult;importorg.springframework.validation.annotation.Validated;importorg.springframework.web.bind.annotation.*;importjavax.servlet.http.HttpServletRequest;importjavax.validation.constraints.Min;importjavax.validation.constraints.NotEmpty;importjavax.validation.constraints.NotNull;importjava.util.Date;/**
 * <p> 参数定义方式:直接在方法形参中,写一个个的入参
 */@Validated@RestController@RequestMapping("/param_validate/parameter")publicclassParameterValidateController{/**
     *<p> 接口定义方式:直接在方法形参中,写一个个的入参
     *<p> 没有入参校验
     *<p> 调用方在调用接口时不传name参数,也可以进入Controller方法,由于没有入参校验,所以没有传name入参也不会报错
     * <p> 若不传age,则抛异常{@link org.springframework.web.bind.MissingServletRequestParameterException},可以自行通过{@link ExceptionHandler}进行捕获处理
     */@RequestMapping(path ="/test1_no_validate")publicStringtest1(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字段做任何的校验限制。
在这里插入图片描述

1.1.2 用例2

当不传age字段时,请求失败,因为接口定义那里,给age字段添加了

@RequestParam(required = true)

注解
在这里插入图片描述

1.2 添加

@NotNull

等校验注解

这种就是把

JSR303

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

**同时还需要在 Controller 类上添加

@Validated

注解,否则校验注解不生效。
推荐只要是想进行入参的校验,那就把

@Validated

注解添加到你的Controller 类上,防止忘记了**。

代码示例:

packagecom.su.demo.controller.param_validate;importorg.springframework.format.annotation.DateTimeFormat;importorg.springframework.validation.BindingResult;importorg.springframework.validation.annotation.Validated;importorg.springframework.web.bind.annotation.*;importjavax.servlet.http.HttpServletRequest;importjavax.validation.constraints.Min;importjavax.validation.constraints.NotEmpty;importjavax.validation.constraints.NotNull;importjava.util.Date;/**
 * <p> 参数定义方式:直接在方法形参中,写一个个的入参
*/@Validated@RestController@RequestMapping("/param_validate/parameter")publicclassParameterValidateController{/**
     *<p> 参数定义方式:直接在方法形参中,写一个个的入参 (多个参数+多个校验的示例)
     *<p> 这种一般就是把 JSR303 相关的注解直接应用到接口参数上即可。同时还需要在 Controller 类上添加{@link Validated} 注解
     *<p> 注意:必须同时在Controller类上标注 {@link Validated} 注解,否则约束注解不会生效
     * <ul>
     *     <li>如果必填的入参在调用时候没有传入,则会抛出 {@link javax.validation.ConstraintViolationException} 异常</li>
     *     <li>如果调用时候传入的参数格式错误(如给age字段传入aaa这样的非数值),则会抛出 {@link org.springframework.web.method.annotation.MethodArgumentTypeMismatchException} 异常</li>
     * </ul>
     */@RequestMapping(path ="/test2")publicStringtest2(@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

必填参数如果不传入,调用失败
在这里插入图片描述

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

传入的age大小不符合范围校验,调用失败
在这里插入图片描述

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

传入的age类型错误,调用失败
在这里插入图片描述


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

风格的接口,那可以考虑用这种方式

示例代码如下:

packagecom.su.demo.controller.param_validate;importorg.springframework.validation.annotation.Validated;importorg.springframework.web.bind.annotation.PathVariable;importorg.springframework.web.bind.annotation.RequestMapping;importorg.springframework.web.bind.annotation.RestController;importjavax.servlet.http.HttpServletRequest;importjavax.validation.constraints.Min;@Validated@RestController@RequestMapping("/param_validate/path_variable")publicclassPathVariableValidateController{/**
     *<p> PathVariable方式
     * <ul>
     *     <li>如果调用的时候不传type或id参数,则报404</li>
     *     <li>如果传入的id不是数值格式,则抛出{@link javax.validation.ConstraintViolationException}的异常</li>
     * </ul>
     */@RequestMapping(value ="/test1/{type}/{id}")publicStringtest1(@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范围错误

在这里插入图片描述

2.1 用例2:不传type或id

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

404 NotFound

错误
在这里插入图片描述


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

实体

定义方式:在方式形参的位置,直接用一个

实体类

来接收入参

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

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

注解)

代码示例:

packagecom.su.demo.controller.param_validate;importcom.su.demo.bean.dto.*;importcom.su.demo.validate.CompanyEmail;importorg.springframework.validation.BindingResult;importorg.springframework.validation.annotation.Validated;importorg.springframework.web.bind.annotation.*;importjavax.servlet.http.HttpServletRequest;importjavax.validation.Valid;@Validated@RestController@RequestMapping("/param_validate/entity")publicclassEntityValidateController{/**
     *<p> 参数定义方式:直接在方法形参中,用一个实体接收入参
     *<p> 注意:一定要在方法的形参位置添加{@link Validated}注解
     *<p> 也可以使用javax提供的(标准JSR-303规范){@link Valid}注解,效果是一样的,参考下面的 {@link EntityValidateController#test3_valid(User1, HttpServletRequest)} 接口
     * <ul>
     *     <li>如果调用时必填参数没传,或者传入的age字段不是数值格式,则报{@link org.springframework.validation.BindException}异常</li>
     * </ul>
     */@RequestMapping(value ="/test2_validated")publicStringtest2_validated(@ValidatedUser1 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

代码如下:

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

注意,在实体

User1 

里面,每个属性都可以添加相应的(1个多个)校验注解

3.1.1 用例1:

调用时必填入参如果没填,调用失败
在这里插入图片描述

3.1.2 用例2:

调用时入参格式错误,调用失败
在这里插入图片描述

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

注解)

代码示例:

packagecom.su.demo.controller.param_validate;importcom.su.demo.bean.dto.*;importcom.su.demo.validate.CompanyEmail;importorg.springframework.validation.BindingResult;importorg.springframework.validation.annotation.Validated;importorg.springframework.web.bind.annotation.*;importjavax.servlet.http.HttpServletRequest;importjavax.validation.Valid;@Validated@RestController@RequestMapping("/param_validate/entity")publicclassEntityValidateController{/**
     *<p> 参数定义方式:直接在方法形参中,用一个实体接收入参
     *<p> 加上{@link RequestBody}之后,调用方在调用该方法的时候,就只能通过body来传参了
     *<p> 注意:在方法的形参位置添加{@link Valid}注解。且跟上一个方法相比,该方法多了@RequestBody注解,我们尝试通过将参数放在requestBody中,看看会不会进行入参校验
     *<p> 也可以使用 {@link Validated}注解,效果是一样的,参考上面的 {@link EntityValidateController#test2_validated_requestbody(User11, HttpServletRequest)} 接口
     * <ul>
     *     <li>如果调用时必填参数没传,或传入的手机号格式错误,则报{@link org.springframework.web.bind.MethodArgumentNotValidException}异常</li>
     *     <li>如果调用时传入的birth日期格式错误,则报{@link org.springframework.http.converter.HttpMessageNotReadableException}异常</li>
     * </ul>
     */@RequestMapping(value ="/test3_valid_requestbody")publicStringtest3_valid_requestbody(@RequestBody@ValidUser11 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

代码如下:

packagecom.su.demo.bean.dto;importcom.fasterxml.jackson.annotation.JsonFormat;importlombok.Data;importorg.springframework.format.annotation.DateTimeFormat;importjavax.servlet.http.HttpServletRequest;importjavax.validation.constraints.*;importjava.util.Date;/**
 * 简单bean对象
 */@DatapublicclassUser11{@NotBlank(message ="姓名不能为空")privateString name;@Digits(message ="年龄必须是数值", integer =120, fraction =0)@NotNull(message ="年龄不能为空")@Min(value =0, message ="年龄不能小于0")@Max(value =120, message ="年龄不能大于120")privateInteger age;@NotBlank(message ="邮箱不能为空")@Email(message ="邮箱不符合邮箱格式")privateString email;@NotBlank(message ="手机号不能为空")@Pattern(regexp ="^1[3456789]\\d{9}$", message ="手机号格式错误")privateString phone;@NotBlank(message ="备注不能为空")@Size(min =2, max =6, message ="备注长度必须在2~6之间")privateString remark;// todo 这里如果用 @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss"),则无法解决@RequestBody的Date入参格式化问题/**
     *<p> 如果使用{@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")privateDate birth;}

3.2.1 用例1:入参为空

调用时若必填入参没有传入,调用失败
在这里插入图片描述

注意请求入参是放在

body

当中的,并且

Content-type=application/json

3.2.2 用例2:入参格式错误

调用时若入参格式错误,调用失败
在这里插入图片描述

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

注解)

代码示例:

packagecom.su.demo.controller.param_validate;importcom.su.demo.bean.dto.*;importcom.su.demo.validate.CompanyEmail;importorg.springframework.validation.BindingResult;importorg.springframework.validation.annotation.Validated;importorg.springframework.web.bind.annotation.*;importjavax.servlet.http.HttpServletRequest;importjavax.validation.Valid;@Validated@RestController@RequestMapping("/param_validate/entity")publicclassEntityValidateController{/**
     * requestBody方式
     * 测试【嵌套参数】的校验
     * <ul>
     *     <li>如果调用时嵌套的必填参数没传,则报{@link org.springframework.web.bind.MethodArgumentNotValidException}异常</li>
     * </ul>
     */@RequestMapping(value ="/test4_validated_requestbody_nest")publicStringtest4_validated_requestbody_nest(@RequestBody@ValidatedUser2 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个对象的代码如下:

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

注意,上面

User2

类内的dept和hobbyList属性上面,必须使用

@Valid

注解,不能用

@Validated

,因为

@Valid

嵌套验证的功能,而

@Validated

没有。

3.3.1 用例1

调用时若嵌套对象的属性不传,则调用失败
在这里插入图片描述

3.3.2 用例2

嵌套入参正确,调用成功
在这里插入图片描述

3.4(分组校验)

@RequestBody

+

@Validated
  • 实体,接收入参
@Validated

还提供了分组功能,可以在参数验证时,根据不同的分组采用不同的验证机制
比如有如下的

User3

的类:

@DatapublicclassUser3{/**
     * 自增主键id
     */@Null(message ="用户id不能传值", groups =OnSave.class)@NotNull(message ="用户id不能为空", groups =OnUpdate.class)privateLong id;@NotBlank(message ="姓名不能为空", groups ={OnSave.class,OnUpdate.class})privateString name;@NotNull(message ="备注不能为空", groups =OnSave.class)privateString remark;/**
     * 分组
     */publicinterfaceOnSave{}publicinterfaceOnUpdate{}}

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

  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) 示例代码如下:
packagecom.su.demo.controller.param_validate;importcom.su.demo.bean.dto.*;importcom.su.demo.validate.CompanyEmail;importorg.springframework.validation.BindingResult;importorg.springframework.validation.annotation.Validated;importorg.springframework.web.bind.annotation.*;importjavax.servlet.http.HttpServletRequest;importjavax.validation.Valid;@Validated@RestController@RequestMapping("/param_validate/entity")publicclassEntityValidateController{/**
     * 在实际项目中,可能多个方法需要使用同一个DTO类来接收参数,
     * 而不同方法的校验规则很可能是不一样的。这个时候,简单地在DTO类的字段上加约束注解无法解决这个问题。
     * 因此,spring-validation支持了分组校验的功能,专门用来解决这类问题。还是上面的例子,比如保存User的时候,
     * UserId是可空的,但是更新User的时候,UserId的值必须>=10000000000000000L;其它字段的校验规则在两种情况下一样。
     *//**
     * 校验分组 OnSave
     * save时,校验的规则是OnSave,要求必须填写name和remark,但是id可以为空
     */@RequestMapping(value ="test6_group_save")publicStringtest6_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")publicStringtest6_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字段, 则调用失败
在这里插入图片描述

3.4.2 用例2:测试

OnUpdate

分组

调用接口时如果不传id字段, 则调用失败
在这里插入图片描述

3.5(自定义校验)

@RequestBody

+

@Validated

(或

@Valid

) + 实体,接收入参

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

先定义一个名为

CompanyEmail

注解

packagecom.su.demo.validate;importjavax.validation.Constraint;importjavax.validation.Payload;importjava.lang.annotation.Documented;importjava.lang.annotation.Retention;importjava.lang.annotation.Target;importstaticjava.lang.annotation.ElementType.*;importstaticjava.lang.annotation.ElementType.TYPE_USE;importstaticjava.lang.annotation.RetentionPolicy.RUNTIME;/**
 * 自定义的【QQ邮箱】格式校验的注解
 */@Target({METHOD,FIELD,ANNOTATION_TYPE,CONSTRUCTOR,PARAMETER,TYPE_USE})@Retention(RUNTIME)@Documented@Constraint(validatedBy ={CompanyEmailValidator.class})public@interfaceCompanyEmail{Stringmessage()default"必须是QQ邮箱,如[email protected]";Class<?>[]groups()default{};Class<?extendsPayload>[]payload()default{};}

注意上面代码,跟普通的注解不一样的是,我们的这个注解中还通过

@Constraint(validatedBy = {CompanyEmailValidator.class})

,去绑定它对应的验证器

CompanyEmailValidator

,验证器

CompanyEmailValidator

中就定义了具体的验证规则

其中

CompanyEmailValidator

的代码如下:

packagecom.su.demo.validate;importorg.springframework.util.StringUtils;importjavax.validation.ConstraintValidator;importjavax.validation.ConstraintValidatorContext;/**
 * 自定义的QQ邮箱的的验证器
 */publicclassCompanyEmailValidatorimplementsConstraintValidator<CompanyEmail,String>{@OverridepublicbooleanisValid(String value,ConstraintValidatorContext context){// 超简单的验证,如果value不为空且以@qq.com结尾,则返回truereturnnull!= value && value.endsWith("@qq.com");}}

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

packagecom.su.demo.controller.param_validate;importcom.su.demo.bean.dto.*;importcom.su.demo.validate.CompanyEmail;importorg.springframework.validation.BindingResult;importorg.springframework.validation.annotation.Validated;importorg.springframework.web.bind.annotation.*;importjavax.servlet.http.HttpServletRequest;importjavax.validation.Valid;@Validated@RestController@RequestMapping("/param_validate/entity")publicclassEntityValidateController{/**
     *<p> 使用自定义的校验
     *<p> 必须在Controller上添加{@link Validated}注解,否则自定义的校验不起作用
     */@RequestMapping(value ="/test7_custom_validator")publicStringtest7_custom_custom_validator(@CompanyEmailString 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邮箱,调用失败
在这里插入图片描述

3.6

快速失败

校验配置

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

实际上有

两种

校验模式:

  1. 普通模式(默认是这个模式): 会校验完所有的属性,然后返回所有的验证失败信息
  2. 快速失败模式:只要有一个验证失败,则返回。 如果想要配置第二种模式快速失败模式,需要添加如下配置类
packagecom.su.demo.config;importorg.hibernate.validator.HibernateValidator;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;importjavax.validation.Validation;importjavax.validation.Validator;importjavax.validation.ValidatorFactory;/**
 * <p>在上面实体类参数校验的例子中,可以看到会校验所有字段,并将所有不符合校验规则的字段打印出来。这种校验策略是实体类参数校验的普通校验模式(默认的校验模式)。实际上有两种校验模式:
 * <p>①普通模式(默认是这个模式): 会校验完所有的属性,然后返回所有的验证失败信息
 * <p>②快速失败模式: 只要有一个验证失败,则返回。
 *
 * <p> 如果想要配置第二种模式,需要添加如下配置类
 */@ConfigurationpublicclassValidateConfig{@BeanpublicValidatorvalidator(){ValidatorFactory validatorFactory =Validation.byProvider(HibernateValidator.class).configure().failFast(true)// 快速失败模式: 只要有一个验证失败,则返回.buildValidatorFactory();Validator validator = validatorFactory.getValidator();return validator;}}

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

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


4. 方式4:用原生的

HttpServletRequest

接收参数

用原生的

HttpServletRequest

,既可以接收query入参,也可以接收body入参。
但是这种用原生的

HttpServletRequest

来接收入参方式,没有办法结合

@NotNull

等校验注解,对入参进行校验。
如果一定想要进行入参的校验,那就只能在代码中手动的进行判断了。
因此,一般都很少用这种方式来接收入参了。

packagecom.su.demo.controller.param_validate;importcn.hutool.core.io.IoUtil;importcom.alibaba.fastjson.JSON;importcom.su.demo.bean.dto.*;importcom.su.demo.bean.vo.base.ResultCodeEnum;importcom.su.demo.bean.vo.base.ResultVO;importcom.su.demo.validate.CompanyEmail;importorg.springframework.validation.BindingResult;importorg.springframework.validation.annotation.Validated;importorg.springframework.web.bind.annotation.*;importjavax.servlet.ServletInputStream;importjavax.servlet.http.HttpServletRequest;importjavax.validation.Valid;importjava.io.IOException;@Validated@RestController@RequestMapping("/param_validate/entity")publicclassEntityValidateController{/**
     * <ul>
     *     <li><b>入参类别</b>:body参数方式
     *     <li><b>定义方式</b>: 在方式形参的位置,用原生的{@link HttpServletRequest}接收
     *     <li><b>调用方式</b>: 参数放在请求体 body 当中
     *     <li><b>兼容的请求方式</b>:GET POST PUT DELETE</li>
     *     <li><b>适用场景</b>:从技术上来说,这种传参方式同时适用于GET POST PUT DELETE等请求方式,但是正常情况下比较少用这种方式来获取body参数了</li>
     *     <li><b>优点</b>:{@link HttpServletRequest}是整个请求,可以获取到所有的数据.且HttpServletRequest、HttpServletResponse都是内置对象,可以使用</li>
     *     <li><b>缺点</b>:代码中再去从request中拿到参数,老麻烦,并且还没有办法直接将校验的注解应用过来</li>
     * </ul>
     */@RequestMapping(value ="/test10_request")publicResultVOtest10(HttpServletRequest request)throwsIOException{// 从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读取成stringString body =IoUtil.read(inputStream,"UTF-8");System.out.println("body = "+ body);// 用fastjson将json字符串转换成beanUser1 user =JSON.parseObject(body,User1.class);//虽然User1的属性上加了@NotNull等注解,但是并不起作用// 只能在代码中手动校验//        if (null == user.getName()) {//            return new ResultVO(ResultCodeEnum.IllEGAL_PARAMETER, "入参name不能为空");//        }returnnewResultVO(requestMetaInfo +" ---> SUCCESS! user = "+ user);}}

上面代码示例是用原生的

HttpServletRequest

来接收body中的

application/json

入参,其实

HttpServletRequest

同样也可以用来接收

query

入参,只需要在代码中通过

request.getParameter("参数名称")

的方式来获取入参即可。
为了节约篇幅,这里就不再举例了。

用例:
在这里插入图片描述


5. 方式6:用

String

结合

@RequestBody

来接收body入参

这种方式的示例代码如下,它跟上面那种方式一样的缺点——没有办法结合

@NotNull

等校验注解,对入参进行校验。
因此,一般也很少用这种方式来接收入参

/**
     * 直接用一个String接收body参数
     */@RequestMapping(value ="/test11_str")publicResultVOtest11(@RequestBodyString body,HttpServletRequest request)throwsIOException{// 从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字符串转换成beanUser1 user =JSON.parseObject(body,User1.class);//虽然User1的属性上加了@NotNull等注解,但是并不起作用// 只能在代码中手动校验//        if (null == user.getName()) {//            return new ResultVO(ResultCodeEnum.IllEGAL_PARAMETER, "入参name不能为空");//        }returnnewResultVO(requestMetaInfo +" ---> SUCCESS! user = "+ user);}

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

调用用例:
在这里插入图片描述


6. 方式5:用

Map

来接收

RequestBody

入参

这种方式的示例代码如下,它跟上面那种方式一样的缺点——没有办法结合

@NotNull

等校验注解,对入参进行校验。
因此,同样也很少用这种方式来接收入参

但是如果你的接口入参个数多,你又

懒得

定义对应的实例来接收入参,并且也不想对入参进行校验,那你

图方便

的话,也可以选择使用这种方式来接收入参。自行决定

/**
     * 直接用一个Map接收body参数
     */@RequestMapping(value ="/test12_map")publicResultVOtest12(@RequestBodyMap<String,Object> bodyMap,HttpServletRequest request)throwsIOException{// 从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")){returnnewResultVO(ResultCodeEnum.IllEGAL_PARAMETER,"入参name不能为空");}returnnewResultVO(requestMetaInfo +" ---> SUCCESS! bodyMap = "+ bodyMap);}

调用用例:
在这里插入图片描述


结束。

标签: spring boot 后端 java

本文转载自: https://blog.csdn.net/qq_20068025/article/details/135932694
版权归原作者 象牙酥 所有, 如有侵权,请联系我们删除。

“SpringBoot接口入参校验的几种方式”的评论:

还没有评论