前言部分主要是一些说明,会有点啰嗦,若是着急,自行选择跳过。
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区别
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
这个包下。- Hibernate validation是对这个规范的实现,并增加了一些其他校验注解,如
@NotBlank
、@NotEmpty
、@Length
等,它们位于org.hibernate.validator.constraints
这个包下。 - 而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:在方法形参的位置,把
每个参数平铺
开来 - 接口定义方式2:用
占位符
的方式把入参封装到请求路径中 - 接口定义方式3:把入参封装到一个
实体
中 - 接口定义方式4:用原生的
HttpServletRequest
接收参数 - 接口定义方式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
的方式,捕获对应的异常,然后在捕获异常的方法内返回相应的ResultVO对象。@ExceptionHandler
下面是我利用
@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
兼容的调用方式包括:
- query方式传参:即调用方在调用接口时,入参是拼接在接口的URI后面的,如/test_get/requestparam_1
?name=wangwu&age=18
。这种方式在入参个数比较少的GET请求方式中比较常用。 - form-data:即调用方在调用接口时,参数是放在
body
中的,且Content-type=form-data
。 - 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
等请求方式,但是正常情况下一般用在POST
或PUT
请求中
3.1
@Validated
(或
@Valid
) + 实体,接收入参,接收入参
**直接在方法形参中,用一个实体接收入参。
并且注意实体前面没有
@RequestBody
注解,而是只添加了
@Validated
注解或
@Valid
注解。**
兼容的请求方式:
GET
POST
PUT
DELETE
兼容的调用方式包括:
- query方式传参:即调用方在调用接口时,入参是拼接在接口的URI后面的,如/param_validate/entity/test2_validated
?name=wangwu&age=18
。这种方式在入参个数比较少的GET请求方式中比较常用。 - form-data:即调用方在调用接口时,参数是放在
body
中的,且Content-type=form-data
。 - 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
兼容的调用方式:
- 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个对象,下面代码有示例)。
兼容的调用方式:
- 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{}}
(举个例子)我们在业务上可能会有这样的一个处理逻辑:
- 当
User3
对象用来接收新建保存
接口的数据时,要求前端传过来的用户数据中不能有id字段的数据,因为id字段是自增主键,在新建
用户的时候,应该是由数据库自己生成的,而不是前端传过来 - 而当
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
快速失败
校验配置
在上面的代码示例中,可以看到会校验所有字段,并将所有不符合校验规则的字段检测出来。这种校验策略是实体类参数校验的普通校验模式(默认的校验模式)。
实际上有
两种
校验模式:
- 普通模式(默认是这个模式): 会校验完所有的属性,然后返回所有的验证失败信息
- 快速失败模式:只要有一个验证失败,则返回。 如果想要配置第二种模式快速失败模式,需要添加如下配置类
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);}
调用用例:
结束。
版权归原作者 象牙酥 所有, 如有侵权,请联系我们删除。