最近因为某些原因, 需要对原来项目中通过注解实现的参数校验进行升级. 改为使用分组进行校验, 区分增删改查. 去网上看了一下, 结果发现相关文章大都是简单提一下, 实际使用中出现的参数失效的问题反而很多. 而且本来还打算将本文名称改成
SpringBoot参数校验各种失效情景及解决
, 但思考后发现失效的问题较多, 无法一一列举. 所以决定还是将SpringBoot参数校验的各种正确使用方式系统的总结一下, 以供后续自己和他人使用.
SpringBoot参数校验网上已经有很多了, 我这里不详细说明了. 就简单介绍下两注意三步骤
注意:
大致的使用步骤有三个步骤:
pom文件中加入相关启动器
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
请求实体对应属性上面加注解 & controller上加相关注解(主要是@Validated
)
public class Item {
@NotNull(message = "id不能为空")
@Min(value = 1, message = "id必须为正整数")
private Long id;
@NotNull(message = "props不能为空")
@Size(min = 1, message = "props至少要有一个自定义属性")
private String props;
}
全局异常处理
//请求入参为实体类, 嵌套类时调用
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseTemplate MethodArgumentNotValidException(MethodArgumentNotValidException e) {
List<FieldError> fieldErrors = e.getBindingResult().getFieldErrors();
List<String> resultMessageList = fieldErrors.stream()
.map(DefaultMessageSourceResolvable::getDefaultMessage)
.collect(Collectors.toList());
return ResponseTemplate.fail(resultMessageList.toString().substring(1, resultMessageList.toString().length()-1));
}
//请求入参为list集合, 嵌套类时调用
@ExceptionHandler(ConstraintViolationException.class)
public ResponseTemplate handlerValidator(ConstraintViolationException constraintViolationException) {
Set<ConstraintViolation<?>> constraintViolationSets = constraintViolationException.getConstraintViolations();
List<String> resultMessageList = constraintViolationSets.stream()
.map(ConstraintViolation::getMessageTemplate)
.collect(Collectors.toList());
return ResponseTemplate.fail(resultMessageList.toString().substring(1, resultMessageList.toString().length()-1));
}
本文主要介绍post请求时, 入参为下图参数类型时的参数校验方式. 而get请求, 则只需保证在 controller上加
@Validated
并在入参前加入对应的用于校验的注解即可.
在控制类上加@Validated
import com.sx.projectstructure.config.jsr.InsertDO;
import com.sx.projectstructure.config.jsr.UpdateDO;
import com.sx.projectstructure.entity.po.StudentInfoPO;
import com.sx.projectstructure.entity.vo.ResponseTemplate;
import com.sx.projectstructure.service.StudentInfoService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.List;
/**
* @Author chy
* @Date 2023/11/28
* @Description
*/
@RestController
@RequestMapping("/student/info")
@Api(tags = "学生信息接口")
@Validated
public class StudentInfoController {
@Resource
private StudentInfoService studentInfoService;
}
创建四个用于分组校验时使用的接口类
/**
* @Author chy
* @Date 2023/11/28
* @Description
*
* JSR330 删除组标识
*/
public interface DeleteDO {
}
/**
* @Author chy
* @Date 2023/11/28
* @Description
*
* JSR330 删除组标识
*/
public interface DeleteDO {
}
/**
* @Author chy
* @Date 2023/11/28
* @Description
*
* JSR330 查询组标识
*/
public interface SelectDO {
}
/**
* @Author chy
* @Date 2023/11/28
* @Description
*
* JSR 330 修改组标识
*/
public interface UpdateDO {
}
需要简单说明一下使用分组校验的原理: 就是在校验参数时, 我们希望有些参数仅在指定的操作中生效(例如增改).
我们就可以通过对应属性校验注解的groups
参数指定参数校验生效的范围, 值为上面的接口(可以为多个).
然后在controller层通过@Validated(value = InsertDO.class)
来定义当前属于哪种操作并和请求实体中定义的范围进行比对.
属于对应范围后才会进行参数校验. 具体请求方式的校验步骤请见下面的介绍:
请求实体
groups
可以定义校验生效的范围, 表示在操作下用于参数校验的注解才会生效.
@TableName(value = "student_info")
public class StudentInfoPO {
@TableId(type = IdType.AUTO)
private Integer id;
/**
* 姓名
*/
@NotEmpty(message = "菜单名称不可为空", groups = {InsertDO.class, UpdateDO.class})
@HaveNoBlank(message = "字符串中不能含有空格666", groups = {InsertDO.class})
@TableField(value = "name")
private String name;
/**
* 年龄
*/
@NotNull(message = "年龄不可为空", groups = {InsertDO.class, UpdateDO.class})
@TableField(value = "age")
private Integer age;
/**
* 身份证号
*/
@NotEmpty(message = "身份证号不可为空", groups = {InsertDO.class, UpdateDO.class})
@TableField(value = "id_card")
private String idCard;
}
接口方法
@Validated
的value
属性可以指定当前属于哪种操作, 用于和请求实体中生效的范围进行比较, 属于生效范围中才会进行参数校验
@PostMapping
@ApiOperation("测试新增-post请求")
public ResponseTemplate insertStudentInfo(@RequestBody @Validated(value = InsertDO.class) StudentInfoPO studentInfoPO) {
studentInfoService.insertStudentInfo(studentInfoPO);
return ResponseTemplate.success();
}
无参请求 因为使用的基本上都是判空校验的注解, 所以使用无参和实参进行请求, 通过返回信息来判断是否生效
实参请求
ps: 验证controller方法中@Validated
的值为请求实体中groups
属性范围外的值时, 是否生效
修改controller方法中@Validated
注解中value
属性的值
@PostMapping
@ApiOperation("测试新增-post请求-分组")
public ResponseTemplate insertStudentInfo(@RequestBody @Validated(value = SelectDO.class) StudentInfoPO studentInfoPO) {
studentInfoService.insertStudentInfo(studentInfoPO);
return ResponseTemplate.success();
}
通过postman可以看到, 在指定范围外的分组进行校验时, 无参请求, 接口也正常调用, 所以参数校验注解没有生效
请求实体 同一个参数校验注解分组和不分组时不能同时混用, 不分组时需要去掉groups参数相关内容. controller方法中请求实体前使用未分组的注解, 但请求实体具体属性上注解使用分组属性时, 则参数校验不生效 因此建议使用同一个请求实体时不要同时出现这两种方式, 否则可能会出现滥用导致的注解失效的问题.
@TableName(value = "student_info")
public class StudentInfoPO {
@TableId(type = IdType.AUTO)
private Integer id;
/**
* 姓名
*/
@NotEmpty(message = "菜单名称不可为空")
@HaveNoBlank(message = "字符串中不能含有空格666")
@TableField(value = "name")
private String name;
/**
* 年龄
*/
@NotNull(message = "年龄不可为空")
@TableField(value = "age")
private Integer age;
/**
* 身份证号
*/
@NotEmpty(message = "身份证号不可为空")
@TableField(value = "id_card")
private String idCard;
}
接口方法
需要注意: 虽然controller类上面已经有@Validated
, 但请求实体旁还是需要有, 否则不会生效
@PostMapping("/noGroup")
@ApiOperation("测试新增-post请求-不分组")
public ResponseTemplate insertStudentInfo2(@RequestBody @Validated StudentInfoPO studentInfoPO) {
studentInfoService.insertStudentInfo(studentInfoPO);
return ResponseTemplate.success();
}
无参请求
实参请求
本来由普通实体,分组 + List集合,不分组可以推得该部分, 但是由于两个注解不会同时生效(如下代码). 因此想要实现此效果需要自己自定义注解 (后续有时间的话会出专门的文章给出具体实现步骤)
//@Valid和@Validated不会同时生效
@PostMapping("/saveGroup")
@ApiOperation("测试新增-post请求-list集合对象-分组")
public ResponseTemplate insertStudentInfo3(@RequestBody @Valid @Validated(value = InsertDO.class) List<StudentInfoPO> studentInfoPO) {
System.out.println("studentInfoPO = " + studentInfoPO);
return ResponseTemplate.success();
}
请求实体
@TableName(value = "student_info")
public class StudentInfoPO {
@TableId(type = IdType.AUTO)
private Integer id;
/**
* 姓名
*/
@NotEmpty(message = "菜单名称不可为空")
@HaveNoBlank(message = "字符串中不能含有空格666", groups = {InsertDO.class, UpdateDO.class})
@TableField(value = "name")
private String name;
/**
* 年龄
*/
@NotNull(message = "年龄不可为空")
@TableField(value = "age")
private Integer age;
/**
* 身份证号
*/
@NotEmpty(message = "身份证号不可为空")
@TableField(value = "id_card")
private String idCard;
}
controller方法
@PostMapping("/save")
@ApiOperation("测试新增-post请求-list集合对象请求")
public ResponseTemplate insertStudentInfo2(@RequestBody @Valid List<StudentInfoPO> studentInfoPO) {
System.out.println("studentInfoPO = " + studentInfoPO);
return ResponseTemplate.success();
}
无参请求
带参请求
请求实体
这里一定要对嵌套实体的属性加上@Valid
, 用于对被嵌套的实体类进行校验
可以加@NotNull
, 用于嵌套实体为null时的提示, 否则不会提示
@TableName(value = "student_info")
public class StudentInfoPO {
@TableId(type = IdType.AUTO)
private Integer id;
/**
* 姓名
*/
// @NotEmpty(message = "菜单名称不可为空")
// @HaveNoBlank(message = "字符串中不能含有空格666", groups = {InsertDO.class, UpdateDO.class})
@TableField(value = "name")
private String name;
/**
* 年龄
*/
// @NotNull(message = "年龄不可为空", groups = {InsertDO.class, UpdateDO.class})
@TableField(value = "age")
private Integer age;
/**
* 身份证号
*/
// @NotEmpty(message = "身份证号不可为空", groups = {InsertDO.class, UpdateDO.class})
@TableField(value = "id_card")
private String idCard;
@TableField(value = "create_time")
private LocalDateTime createTime;
@TableField(value = "update_time")
private LocalDateTime updateTime;
@Valid
@NotNull(message = "demoDto不能为空")
private DemoDto demoDto;
}
嵌套实体
通过groups
指定生效的范围
public class DemoDto {
@NotNull(message = "嵌套类param属性不能为null",groups = {InsertDO.class, UpdateDO.class})
@Min(value = 2,groups = {InsertDO.class, UpdateDO.class})
private Integer param;
public Integer getParam() {
return param;
}
public void setParam(Integer param) {
this.param = param;
}
}
controller方法
同普通实体分组一样, 分组. @Validated
的value
属性可以对请求实体或者嵌套实体中规定的范围进行比对
@PostMapping
@ApiOperation("测试新增-post请求-分组")
public ResponseTemplate insertStudentInfo(@RequestBody @Validated(value = InsertDO.class) StudentInfoPO studentInfoPO) {
// studentInfoService.insertStudentInfo(studentInfoPO);
return ResponseTemplate.success();
}
传入嵌套实体, 传入指定参数, 但传入不合规的值
传入嵌套实体, 传入指定参数, 传入合规的值
请求实体
这里一定要对嵌套实体对应属性加上@Valid
, 用于对被嵌套的实体类进行校验
建议可以追加@NotNull
注解, 用于嵌套属性为null时的消息提示,
否则嵌套属性不传时, 则不会对嵌套属性和其对应的嵌套实体内的属性进行校验.
@TableName(value = "student_info")
public class StudentInfoPO {
@TableId(type = IdType.AUTO)
private Integer id;
/**
* 姓名
*/
@TableField(value = "name")
private String name;
/**
* 年龄
*/
@TableField(value = "age")
private Integer age;
/**
* 身份证号
*/
@TableField(value = "id_card")
private String idCard;
/**
* 嵌套属性
*/
@Valid
@NotNull(message = "demoDto不能为空")
private DemoDto demoDto;
}
嵌套实体 可以加入需要进行校验的属性
public class DemoDto {
@NotNull(message = "嵌套类param属性不能为null")
@Min(2)
private Integer param;
public Integer getParam() {
return param;
}
public void setParam(Integer param) {
this.param = param;
}
}
controller方法 这里同普通实体, 不分组的书写方式
@PostMapping("/noGroup")
@ApiOperation("测试新增-post请求-不分组")
public ResponseTemplate insertStudentInfo2(@RequestBody @Validated StudentInfoPO studentInfoPO) {
studentInfoService.insertStudentInfo(studentInfoPO);
return ResponseTemplate.success();
}
没有传入嵌套实体时请求接口
传入嵌套实体, 但不传指定参数时
传入嵌套实体, 传入指定参数
传入嵌套实体, 传入按照要求的指定参数
get请求很简单, 只需要在controller方法上加上@Validated
, 在指定参数前加上校验注解即可
@RestController
@RequestMapping("/student/info")
@Validated
public class StudentInfoController {
@GetMapping("checkParam")
public String checkParam(@RequestParam @Max(value = 99, message = "不能大于99岁") Integer age) {
return "ok";
}
@GetMapping("checkPath/{id}")
public String checkPath(@PathVariable @Pattern(regexp = "^[0-9]*$", message = "id参数值必须是正整数") String id) {
return "ok";
}
}
post请求常用参数校验方式总结
参考: https://blog.csdn.net/Zong_0915/article/details/126649671 https://blog.csdn.net/sun_mu_one/article/details/122056101 https://zhuanlan.zhihu.com/p/646379747