一、@Valid和@Validated的介绍

1.引入jar包

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-validation</artifactId>
  4. </dependency>

2. @Valid和@Validated的作用

@Validated、@Valid 等校验注解来替代手动对参数进行校验,简单来说就是将参数校验和业务逻辑代码分开。

3.@Valid和@Validated的区别

  1. @Valid:没有分组的功能。
  2. @Valid:可以用在方法、构造函数、方法参数和成员属性(字段)上
  3. @Validated:提供了一个分组功能,可以在入参验证时,根据不同的分组采用不同的验证机制
  4. @Validated:可以用在类型、方法和方法参数上。但是不能用在成员属性(字段)上

4.常用的参数校验注解

6.@Valid和@Validated的使用 - 图1

二、@Valid和@Validated的使用

1. 捕获全局异常配置类

因为使用注解检验参数会抛出异常,而这个异常是在方法中捕获不到的所以需要全局捕获异常,再根据异常类型提示相应的提示。

  1. package com.fwy.corebasic.common.handlerexception;
  2. import com.fwy.common.help.DoResult;
  3. import com.fwy.common.help.DoResultType;
  4. import com.fwy.common.utils.CommonResponse;
  5. import com.fwy.common.utils.LogUtil;
  6. import org.apache.commons.lang3.StringUtils;
  7. import org.springframework.validation.BindException;
  8. import org.springframework.validation.FieldError;
  9. import org.springframework.web.HttpRequestMethodNotSupportedException;
  10. import org.springframework.web.bind.MethodArgumentNotValidException;
  11. import org.springframework.web.bind.annotation.ControllerAdvice;
  12. import org.springframework.web.bind.annotation.ExceptionHandler;
  13. import org.springframework.web.bind.annotation.ResponseBody;
  14. import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
  15. import javax.servlet.http.HttpServletRequest;
  16. import javax.servlet.http.HttpServletResponse;
  17. import javax.validation.ConstraintViolation;
  18. import javax.validation.ConstraintViolationException;
  19. import java.util.ArrayList;
  20. import java.util.Arrays;
  21. import java.util.List;
  22. import java.util.concurrent.atomic.AtomicBoolean;
  23. /**
  24. * @author 前端接口和后台参数校验
  25. */
  26. @ControllerAdvice
  27. public class MyExceptionHandler {
  28. public final static String[] INTERFACE_PATH_ARRAY = {"api","wxApi"};
  29. @ResponseBody
  30. @ExceptionHandler(value = {BindException.class,MethodArgumentNotValidException.class})
  31. public Object validated(Exception e,HttpServletRequest request) {
  32. LogUtil.warn("参数校验提示:{}",request.getRequestURL(), e);
  33. List<FieldError> fieldErrors = null;
  34. if (e instanceof BindException){
  35. BindException e1=(BindException) e;
  36. fieldErrors= e1.getBindingResult().getFieldErrors();
  37. }else {
  38. MethodArgumentNotValidException e2= (MethodArgumentNotValidException) e;
  39. fieldErrors =e2.getBindingResult().getFieldErrors();
  40. }
  41. List<String> validationResults = new ArrayList<>();
  42. for (FieldError fieldError : fieldErrors) {
  43. validationResults.add(fieldError.getDefaultMessage());
  44. }
  45. String messages = StringUtils.join(validationResults.toArray(), ";");
  46. return returnMessage(request,messages);
  47. }
  48. @ResponseBody
  49. @ExceptionHandler(ConstraintViolationException.class)
  50. public Object paramException(ConstraintViolationException e,HttpServletRequest request){
  51. LogUtil.warn("参数校验提示:{}",e);
  52. List<String> msgList = new ArrayList<>();
  53. for (ConstraintViolation<?> constraintViolation : e.getConstraintViolations()) {
  54. msgList.add(constraintViolation.getMessage());
  55. }
  56. String messages = StringUtils.join(msgList.toArray(), ";");
  57. return returnMessage(request,messages);
  58. }
  59. /**
  60. * 违反约束异常处理
  61. * @param e
  62. * @return
  63. */
  64. // @ExceptionHandler(ConstraintViolationException.class)
  65. // @ResponseBody
  66. // public Object handConstraintViolationException(ConstraintViolationException e) {
  67. // LogUtil.error(e.getMessage(), e);
  68. // return e;
  69. // }
  70. // @ExceptionHandler(AbstractHandlerMethodExceptionResolver.class)
  71. // @ResponseBody
  72. // public Object methodWayHandle(ExceptionHandlerExceptionResolver e,HttpServletRequest request){
  73. // LogUtil.error("前端传入{}参数不符合接口格式",e.getApplicationContext());
  74. // return returnMessage(request,"错误");
  75. // }
  76. @ExceptionHandler(MethodArgumentTypeMismatchException.class)
  77. @ResponseBody
  78. public Object methodParamHand(MethodArgumentTypeMismatchException e, HttpServletRequest request){
  79. LogUtil.error("前端传入{}参数不符合接口格式,提示信息{}",e.getName(),e.getMessage());
  80. return returnMessage(request,"前端传入参数"+e.getName()+"不符合接口格式");
  81. }
  82. @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
  83. public Object handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException e,
  84. HttpServletRequest request) {
  85. String requestURI = request.getRequestURI();
  86. LogUtil.error("请求地址'{}',不支持'{}'请求", requestURI, e.getMethod());
  87. return returnMessage(request,"请求地址'"+requestURI+"',不支持'"+e.getMethod()+"'请求");
  88. }
  89. @ExceptionHandler(Exception.class)
  90. @ResponseBody
  91. public Object exceptionHand(Exception e, HttpServletRequest request, HttpServletResponse response) {
  92. LogUtil.error("发生异常的请求地址{}",request.getRequestURL(),e);
  93. return returnMessage(request,"系统异常");
  94. }
  95. public static Object returnMessage(HttpServletRequest request,String message){
  96. String url = request.getRequestURI();
  97. AtomicBoolean flag = new AtomicBoolean(false);
  98. Arrays.stream(INTERFACE_PATH_ARRAY).forEach(path -> {
  99. if (url.contains(path)) {
  100. flag.set(true);
  101. }
  102. });
  103. if(flag.get()){
  104. DoResult result = new DoResult();
  105. result.setStateMsg(message);
  106. result.setStateType(DoResultType.fail);
  107. return result;
  108. }else {
  109. CommonResponse result = new CommonResponse();
  110. result.setMsg(message);
  111. result.setCode(DoResultType.fail.getValue());
  112. return result;
  113. }
  114. }
  115. }

2.单个接口参数校验

  1. /****
  2. * description: 获取学习试题数据接口
  3. * version: 1.0 ->
  4. * date: 2022/5/17 11:45
  5. * author: xiaYZ
  6. * iteration: 迭代说明
  7. * @param deptId 部门id
  8. * @param weiXinUserId 微信用户id
  9. * @param businessId 业务id
  10. * @return com.fwy.common.help.DoResult
  11. */
  12. @GetMapping("getStudyQuestionPaper")
  13. @Validated
  14. public DoResult getStudyQuestionPaper(@NotNull(message = "部门id不能为空") Long deptId,
  15. @NotNull(message = "微信用户id不能空") Long weiXinUserId,
  16. @NotNull(message = "业务id不能为空") Long businessId){
  17. DoResult result = new DoResult();
  18. return result;
  19. }

如图展示,当业务id为空是提示参数校验信息
6.@Valid和@Validated的使用 - 图2

3.接口类参数校验提示

  1. /***
  2. * description: saveStageQuestion
  3. * version: 1.0
  4. * date: 2020/4/24 17:58
  5. * author: objcat
  6. * @param questionBank
  7. * @return com.jrwp.common.help.CommonResponse
  8. */
  9. @ResponseBody
  10. @RequestMapping("saveQuestion")
  11. @Description("保存题库数据")
  12. public CommonResponse saveQuestion(@Validated QuestionBank questionBank){
  13. CommonResponse response = new CommonResponse();
  14. return response;
  15. }
  1. @TableName(value ="QUESTION_BANK")
  2. @KeySequence(value = "SEQ_QUESTION_BANK")
  3. @Data
  4. public class QuestionBank implements Serializable {
  5. public static final String REDIS_KEY = "QUESTION_BANK";
  6. /**
  7. * 主键
  8. */
  9. @TableId
  10. private Long id;
  11. /**
  12. * 知识体系id(数据字典中)
  13. */
  14. @TableField(value = "KNOWLEDGE_ID")
  15. private Long knowledgeId;
  16. /**
  17. * 目类型(0单选题,1多选题)
  18. */
  19. @TableField(value = "QUESTION_TYPE")
  20. private Integer questionType;
  21. /**
  22. * 是否启用(0否,1是)
  23. */
  24. @TableField(value = "IS_START")
  25. private Integer isStart;
  26. /**
  27. * 题目标题
  28. */
  29. @TableField(value = "TITLE")
  30. @Length(max = 500,message = "题目标题长度不能超过500")
  31. private String title;
  32. /**
  33. * 题目答案
  34. */
  35. @TableField(value = "ANSWER")
  36. @Length(max = 20,message = "题目答案长度不能超过20")
  37. private String answer;
  38. /**
  39. * 说明
  40. */
  41. @TableField(value = "REMARKS")
  42. @Length(max = 500,message = "说明长度不能超过500")
  43. private String remarks;
  44. /**
  45. * 题目选项用|分割
  46. */
  47. @TableField(value = "OPTIONS")
  48. @Length(max = 500,message = "题目选项长度不能超过500")
  49. private String options;
  50. /**
  51. * 创建时间
  52. */
  53. @TableField(value = "CREATE_TIME")
  54. @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8",locale = "zh")
  55. private Date createTime;
  56. /**
  57. * 知识体系名称
  58. */
  59. @TableField(value = "KNOWLEDGE_NAME")
  60. private String knowledgeName;
  61. /**
  62. * 题目图片路径
  63. */
  64. @TableField(value = "PhOTO_URL")
  65. private String photoUrl;
  66. @TableField(exist = false)
  67. private static final long serialVersionUID = 1L;
  68. }

如图提示:当数据字段长度超过注解中注释的则会提示信息
6.@Valid和@Validated的使用 - 图3

4.嵌套类注解提示

当我们需要校验嵌套类时,如A包含B,C两个类(A:{B,C}),需要检验B中b字段。

4.1. 实体类数据

  1. @Data
  2. public class SubmitStudyParam {
  3. /**
  4. * 学习记录
  5. */
  6. @Valid
  7. @NotNull(message = "学习记录不能为空")
  8. private StudyRecordApi studyRecordApi;
  9. /**
  10. * 业务名称
  11. */
  12. @NotEmpty(message = "业务名称不能为空")
  13. private String businessName;
  14. /**
  15. * 部门id
  16. */
  17. @NotNull(message = "部门id不能为空")
  18. private Long deptId;
  19. /**
  20. * 部门名称
  21. */
  22. @NotEmpty(message = "部门名称不能为空")
  23. private String deptName;
  24. /**
  25. * 当前刷题id
  26. */
  27. private Long nowQuestionId;
  28. }
  1. @Data
  2. public class StudyRecordApi implements Serializable {
  3. /**
  4. * 主键
  5. */
  6. private Long id;
  7. /**
  8. * 注册用户id
  9. */
  10. @NotNull(message = "注册用户id不能为空")
  11. private Long registerUserId;
  12. /**
  13. * 注册用户姓名
  14. */
  15. @NotEmpty(message = "注册用户姓名不能为空")
  16. private String registerUserName;
  17. /**
  18. * 学习类型(0刷题学习,1文档学习,2视频学习)
  19. */
  20. @NotNull(message = "学习类型不能为空")
  21. private Integer type;
  22. /**
  23. * 事件id,不同类型对应不同表,0时对应刷题学习表,1,2时对应文档表
  24. */
  25. private Long eventId;
  26. /**
  27. * 学习开始时间
  28. */
  29. private Date startTime;
  30. /**
  31. * 开始学习时间字符串
  32. */
  33. @NotEmpty(message = "开始学习时间不能为空")
  34. private String startTimeStr;
  35. /**
  36. * 学习结束时间
  37. */
  38. private Date endTime;
  39. /**
  40. * 学习结束时间字符串
  41. */
  42. @NotEmpty(message = "学习结束时间不能为空")
  43. private String endTimeStr;
  44. /**
  45. * 创建时间
  46. */
  47. private Date createTime;
  48. /**
  49. * 申请记录id
  50. */
  51. @NotNull(message = "申请记录id不能为空")
  52. private Long applyId;
  53. private static final long serialVersionUID = 1L;
  54. }

4.2.接口校验

  1. /****
  2. * description: 提交文档学习
  3. * version: 1.0 ->
  4. * date: 2022/5/18 11:50
  5. * author: xiaYZ
  6. * iteration: 迭代说明
  7. * @param submitStudyParam
  8. * @return com.fwy.common.help.DoResult
  9. */
  10. @ResponseBody
  11. @PostMapping("submitStudyFile")
  12. public DoResult submitStudyFile(@Validated @RequestBody SubmitStudyParam submitStudyParam){
  13. DoResult result = new DoResult();
  14. boolean flag = studyService.submitStudyFile(submitStudyParam);
  15. if(flag){
  16. result.setStateMsg("提交文档学习成功");
  17. result.setStateType(DoResultType.success);
  18. }else{
  19. result.setStateMsg("提交文档学习失败");
  20. result.setStateType(DoResultType.fail);
  21. }
  22. return result;
  23. }

4.3. 接口调用

6.@Valid和@Validated的使用 - 图4

6.@Valid和@Validated的使用 - 图5

5. 分组校验

5.1 校验的实体类数据

  1. @Data
  2. public class SubmitStudyParam {
  3. /**
  4. * 练习题目
  5. */
  6. @NotNull(message = "练习题目不能为空",groups = {StudyService.class})
  7. @Valid
  8. private PracticePaper practicePaper;
  9. /**
  10. * 学习记录
  11. */
  12. @Valid
  13. @NotNull(message = "学习记录不能为空")
  14. private StudyRecordApi studyRecordApi;
  15. /**
  16. * 业务名称
  17. */
  18. @NotEmpty(message = "业务名称不能为空")
  19. private String businessName;
  20. /**
  21. * 部门id
  22. */
  23. @NotNull(message = "部门id不能为空")
  24. private Long deptId;
  25. /**
  26. * 部门名称
  27. */
  28. @NotEmpty(message = "部门名称不能为空")
  29. private String deptName;
  30. /**
  31. * 当前刷题id
  32. */
  33. private Long nowQuestionId;
  34. }

注意:PracticePaper类注解中有一个group参数

5.2 接口校验

  1. /****
  2. * description: 提交文档学习
  3. * version: 1.0 ->
  4. * date: 2022/5/18 11:50
  5. * author: xiaYZ
  6. * iteration: 迭代说明
  7. * @param submitStudyParam
  8. * @return com.fwy.common.help.DoResult
  9. */
  10. @ResponseBody
  11. @PostMapping("submitStudyFile")
  12. public DoResult submitStudyFile(@Validated @RequestBody SubmitStudyParam submitStudyParam){
  13. DoResult result = new DoResult();
  14. boolean flag = studyService.submitStudyFile(submitStudyParam);
  15. if(flag){
  16. result.setStateMsg("提交文档学习成功");
  17. result.setStateType(DoResultType.success);
  18. }else{
  19. result.setStateMsg("提交文档学习失败");
  20. result.setStateType(DoResultType.fail);
  21. }
  22. return result;
  23. }
  1. /****
  2. * description: 提交学习刷题试卷接口
  3. * version: 1.0 ->
  4. * date: 2022/5/18 10:26
  5. * author: xiaYZ
  6. * iteration: 迭代说明
  7. * @param submitStudyParam 提交学习刷题试卷参数
  8. * @return com.fwy.common.help.DoResult
  9. */
  10. @ResponseBody
  11. @PostMapping("submitStudyPaper")
  12. public DoResult submitStudyPaper(@Validated({StudyService.class}) @RequestBody SubmitStudyParam submitStudyParam){
  13. DoResult result = new DoResult();
  14. boolean flag = studyService.submitStudyPaper(submitStudyParam);
  15. if(flag){
  16. result.setStateMsg("提交学习试卷成功");
  17. result.setStateType(DoResultType.success);
  18. }else{
  19. result.setStateMsg("提交学习试卷失败");
  20. result.setStateType(DoResultType.fail);
  21. }
  22. return result;
  23. }

两个接口都使用了SubmitStudyParam 作为接口参数,但是“提交学习刷题试卷接口”,我需要它校验“练习题目类”是否为空,但是“提交文档学习接口”则不需要,所以我使用了@Validated中的group参数

5.3 接口调用

1.提交学习刷题试卷接口演示

6.@Valid和@Validated的使用 - 图6
6.@Valid和@Validated的使用 - 图7

2.提交文档学习接口演示

6.@Valid和@Validated的使用 - 图8
6.@Valid和@Validated的使用 - 图9

6.分组校验和嵌套校验不能一起使用

换句话说当你使用@Valid做嵌套检验是,再使用group参数做分组校验时,它不能校验@Valid注解类中的字段

刷题学习类的校验提示

  1. @Data
  2. public class PracticePaper implements Serializable {
  3. /**
  4. * 主键
  5. */
  6. private Long id;
  7. /**
  8. * 注册用户id
  9. */
  10. @NotNull(message = "注册用户id不能为空")
  11. private Long registerUserId;
  12. /**
  13. * 题目id列表
  14. */
  15. @NotEmpty(message = "题目id列表不能为空")
  16. private String questionIdList;
  17. /**
  18. * 提交答案列表
  19. */
  20. @NotEmpty(message = "提交答案列表不能为空")
  21. private String submitAnswerList;
  22. /**
  23. * 实际答案列表
  24. */
  25. @NotEmpty(message = "实际答案列表不能为空")
  26. private String answerList;
  27. /**
  28. * 创建时间
  29. */
  30. private Date createTime;
  31. private static final long serialVersionUID = 1L;
  32. }

解释:如图我这里的开始学习时间字段为空,但是提交接口后没有为空的提示,直接报错了
6.@Valid和@Validated的使用 - 图10
6.@Valid和@Validated的使用 - 图11