一、@Valid和@Validated的介绍
1.引入jar包
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId></dependency>
2. @Valid和@Validated的作用
@Validated、@Valid 等校验注解来替代手动对参数进行校验,简单来说就是将参数校验和业务逻辑代码分开。
3.@Valid和@Validated的区别
- @Valid:没有分组的功能。
- @Valid:可以用在方法、构造函数、方法参数和成员属性(字段)上
- @Validated:提供了一个分组功能,可以在入参验证时,根据不同的分组采用不同的验证机制
- @Validated:可以用在类型、方法和方法参数上。但是不能用在成员属性(字段)上
4.常用的参数校验注解

二、@Valid和@Validated的使用
1. 捕获全局异常配置类
因为使用注解检验参数会抛出异常,而这个异常是在方法中捕获不到的所以需要全局捕获异常,再根据异常类型提示相应的提示。
package com.fwy.corebasic.common.handlerexception;import com.fwy.common.help.DoResult;import com.fwy.common.help.DoResultType;import com.fwy.common.utils.CommonResponse;import com.fwy.common.utils.LogUtil;import org.apache.commons.lang3.StringUtils;import org.springframework.validation.BindException;import org.springframework.validation.FieldError;import org.springframework.web.HttpRequestMethodNotSupportedException;import org.springframework.web.bind.MethodArgumentNotValidException;import org.springframework.web.bind.annotation.ControllerAdvice;import org.springframework.web.bind.annotation.ExceptionHandler;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import javax.validation.ConstraintViolation;import javax.validation.ConstraintViolationException;import java.util.ArrayList;import java.util.Arrays;import java.util.List;import java.util.concurrent.atomic.AtomicBoolean;/*** @author 前端接口和后台参数校验*/@ControllerAdvicepublic class MyExceptionHandler {public final static String[] INTERFACE_PATH_ARRAY = {"api","wxApi"};@ResponseBody@ExceptionHandler(value = {BindException.class,MethodArgumentNotValidException.class})public Object validated(Exception e,HttpServletRequest request) {LogUtil.warn("参数校验提示:{}",request.getRequestURL(), e);List<FieldError> fieldErrors = null;if (e instanceof BindException){BindException e1=(BindException) e;fieldErrors= e1.getBindingResult().getFieldErrors();}else {MethodArgumentNotValidException e2= (MethodArgumentNotValidException) e;fieldErrors =e2.getBindingResult().getFieldErrors();}List<String> validationResults = new ArrayList<>();for (FieldError fieldError : fieldErrors) {validationResults.add(fieldError.getDefaultMessage());}String messages = StringUtils.join(validationResults.toArray(), ";");return returnMessage(request,messages);}@ResponseBody@ExceptionHandler(ConstraintViolationException.class)public Object paramException(ConstraintViolationException e,HttpServletRequest request){LogUtil.warn("参数校验提示:{}",e);List<String> msgList = new ArrayList<>();for (ConstraintViolation<?> constraintViolation : e.getConstraintViolations()) {msgList.add(constraintViolation.getMessage());}String messages = StringUtils.join(msgList.toArray(), ";");return returnMessage(request,messages);}/*** 违反约束异常处理* @param e* @return*/// @ExceptionHandler(ConstraintViolationException.class)// @ResponseBody// public Object handConstraintViolationException(ConstraintViolationException e) {// LogUtil.error(e.getMessage(), e);// return e;// }// @ExceptionHandler(AbstractHandlerMethodExceptionResolver.class)// @ResponseBody// public Object methodWayHandle(ExceptionHandlerExceptionResolver e,HttpServletRequest request){// LogUtil.error("前端传入{}参数不符合接口格式",e.getApplicationContext());// return returnMessage(request,"错误");// }@ExceptionHandler(MethodArgumentTypeMismatchException.class)@ResponseBodypublic Object methodParamHand(MethodArgumentTypeMismatchException e, HttpServletRequest request){LogUtil.error("前端传入{}参数不符合接口格式,提示信息{}",e.getName(),e.getMessage());return returnMessage(request,"前端传入参数"+e.getName()+"不符合接口格式");}@ExceptionHandler(HttpRequestMethodNotSupportedException.class)public Object handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException e,HttpServletRequest request) {String requestURI = request.getRequestURI();LogUtil.error("请求地址'{}',不支持'{}'请求", requestURI, e.getMethod());return returnMessage(request,"请求地址'"+requestURI+"',不支持'"+e.getMethod()+"'请求");}@ExceptionHandler(Exception.class)@ResponseBodypublic Object exceptionHand(Exception e, HttpServletRequest request, HttpServletResponse response) {LogUtil.error("发生异常的请求地址{}",request.getRequestURL(),e);return returnMessage(request,"系统异常");}public static Object returnMessage(HttpServletRequest request,String message){String url = request.getRequestURI();AtomicBoolean flag = new AtomicBoolean(false);Arrays.stream(INTERFACE_PATH_ARRAY).forEach(path -> {if (url.contains(path)) {flag.set(true);}});if(flag.get()){DoResult result = new DoResult();result.setStateMsg(message);result.setStateType(DoResultType.fail);return result;}else {CommonResponse result = new CommonResponse();result.setMsg(message);result.setCode(DoResultType.fail.getValue());return result;}}}
2.单个接口参数校验
/***** description: 获取学习试题数据接口* version: 1.0 ->* date: 2022/5/17 11:45* author: xiaYZ* iteration: 迭代说明* @param deptId 部门id* @param weiXinUserId 微信用户id* @param businessId 业务id* @return com.fwy.common.help.DoResult*/@GetMapping("getStudyQuestionPaper")@Validatedpublic DoResult getStudyQuestionPaper(@NotNull(message = "部门id不能为空") Long deptId,@NotNull(message = "微信用户id不能空") Long weiXinUserId,@NotNull(message = "业务id不能为空") Long businessId){DoResult result = new DoResult();return result;}
如图展示,当业务id为空是提示参数校验信息
3.接口类参数校验提示
/**** description: saveStageQuestion* version: 1.0* date: 2020/4/24 17:58* author: objcat* @param questionBank* @return com.jrwp.common.help.CommonResponse*/@ResponseBody@RequestMapping("saveQuestion")@Description("保存题库数据")public CommonResponse saveQuestion(@Validated QuestionBank questionBank){CommonResponse response = new CommonResponse();return response;}
@TableName(value ="QUESTION_BANK")@KeySequence(value = "SEQ_QUESTION_BANK")@Datapublic class QuestionBank implements Serializable {public static final String REDIS_KEY = "QUESTION_BANK";/*** 主键*/@TableIdprivate Long id;/*** 知识体系id(数据字典中)*/@TableField(value = "KNOWLEDGE_ID")private Long knowledgeId;/*** 目类型(0单选题,1多选题)*/@TableField(value = "QUESTION_TYPE")private Integer questionType;/*** 是否启用(0否,1是)*/@TableField(value = "IS_START")private Integer isStart;/*** 题目标题*/@TableField(value = "TITLE")@Length(max = 500,message = "题目标题长度不能超过500")private String title;/*** 题目答案*/@TableField(value = "ANSWER")@Length(max = 20,message = "题目答案长度不能超过20")private String answer;/*** 说明*/@TableField(value = "REMARKS")@Length(max = 500,message = "说明长度不能超过500")private String remarks;/*** 题目选项用|分割*/@TableField(value = "OPTIONS")@Length(max = 500,message = "题目选项长度不能超过500")private String options;/*** 创建时间*/@TableField(value = "CREATE_TIME")@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8",locale = "zh")private Date createTime;/*** 知识体系名称*/@TableField(value = "KNOWLEDGE_NAME")private String knowledgeName;/*** 题目图片路径*/@TableField(value = "PhOTO_URL")private String photoUrl;@TableField(exist = false)private static final long serialVersionUID = 1L;}
如图提示:当数据字段长度超过注解中注释的则会提示信息
4.嵌套类注解提示
当我们需要校验嵌套类时,如A包含B,C两个类(A:{B,C}),需要检验B中b字段。
4.1. 实体类数据
@Datapublic class SubmitStudyParam {/*** 学习记录*/@Valid@NotNull(message = "学习记录不能为空")private StudyRecordApi studyRecordApi;/*** 业务名称*/@NotEmpty(message = "业务名称不能为空")private String businessName;/*** 部门id*/@NotNull(message = "部门id不能为空")private Long deptId;/*** 部门名称*/@NotEmpty(message = "部门名称不能为空")private String deptName;/*** 当前刷题id*/private Long nowQuestionId;}
@Datapublic class StudyRecordApi implements Serializable {/*** 主键*/private Long id;/*** 注册用户id*/@NotNull(message = "注册用户id不能为空")private Long registerUserId;/*** 注册用户姓名*/@NotEmpty(message = "注册用户姓名不能为空")private String registerUserName;/*** 学习类型(0刷题学习,1文档学习,2视频学习)*/@NotNull(message = "学习类型不能为空")private Integer type;/*** 事件id,不同类型对应不同表,0时对应刷题学习表,1,2时对应文档表*/private Long eventId;/*** 学习开始时间*/private Date startTime;/*** 开始学习时间字符串*/@NotEmpty(message = "开始学习时间不能为空")private String startTimeStr;/*** 学习结束时间*/private Date endTime;/*** 学习结束时间字符串*/@NotEmpty(message = "学习结束时间不能为空")private String endTimeStr;/*** 创建时间*/private Date createTime;/*** 申请记录id*/@NotNull(message = "申请记录id不能为空")private Long applyId;private static final long serialVersionUID = 1L;}
4.2.接口校验
/***** description: 提交文档学习* version: 1.0 ->* date: 2022/5/18 11:50* author: xiaYZ* iteration: 迭代说明* @param submitStudyParam* @return com.fwy.common.help.DoResult*/@ResponseBody@PostMapping("submitStudyFile")public DoResult submitStudyFile(@Validated @RequestBody SubmitStudyParam submitStudyParam){DoResult result = new DoResult();boolean flag = studyService.submitStudyFile(submitStudyParam);if(flag){result.setStateMsg("提交文档学习成功");result.setStateType(DoResultType.success);}else{result.setStateMsg("提交文档学习失败");result.setStateType(DoResultType.fail);}return result;}
4.3. 接口调用


5. 分组校验
5.1 校验的实体类数据
@Datapublic class SubmitStudyParam {/*** 练习题目*/@NotNull(message = "练习题目不能为空",groups = {StudyService.class})@Validprivate PracticePaper practicePaper;/*** 学习记录*/@Valid@NotNull(message = "学习记录不能为空")private StudyRecordApi studyRecordApi;/*** 业务名称*/@NotEmpty(message = "业务名称不能为空")private String businessName;/*** 部门id*/@NotNull(message = "部门id不能为空")private Long deptId;/*** 部门名称*/@NotEmpty(message = "部门名称不能为空")private String deptName;/*** 当前刷题id*/private Long nowQuestionId;}
注意:PracticePaper类注解中有一个group参数
5.2 接口校验
/***** description: 提交文档学习* version: 1.0 ->* date: 2022/5/18 11:50* author: xiaYZ* iteration: 迭代说明* @param submitStudyParam* @return com.fwy.common.help.DoResult*/@ResponseBody@PostMapping("submitStudyFile")public DoResult submitStudyFile(@Validated @RequestBody SubmitStudyParam submitStudyParam){DoResult result = new DoResult();boolean flag = studyService.submitStudyFile(submitStudyParam);if(flag){result.setStateMsg("提交文档学习成功");result.setStateType(DoResultType.success);}else{result.setStateMsg("提交文档学习失败");result.setStateType(DoResultType.fail);}return result;}
/***** description: 提交学习刷题试卷接口* version: 1.0 ->* date: 2022/5/18 10:26* author: xiaYZ* iteration: 迭代说明* @param submitStudyParam 提交学习刷题试卷参数* @return com.fwy.common.help.DoResult*/@ResponseBody@PostMapping("submitStudyPaper")public DoResult submitStudyPaper(@Validated({StudyService.class}) @RequestBody SubmitStudyParam submitStudyParam){DoResult result = new DoResult();boolean flag = studyService.submitStudyPaper(submitStudyParam);if(flag){result.setStateMsg("提交学习试卷成功");result.setStateType(DoResultType.success);}else{result.setStateMsg("提交学习试卷失败");result.setStateType(DoResultType.fail);}return result;}
两个接口都使用了SubmitStudyParam 作为接口参数,但是“提交学习刷题试卷接口”,我需要它校验“练习题目类”是否为空,但是“提交文档学习接口”则不需要,所以我使用了@Validated中的group参数
5.3 接口调用
1.提交学习刷题试卷接口演示


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


6.分组校验和嵌套校验不能一起使用
换句话说当你使用@Valid做嵌套检验是,再使用group参数做分组校验时,它不能校验@Valid注解类中的字段
刷题学习类的校验提示
@Datapublic class PracticePaper implements Serializable {/*** 主键*/private Long id;/*** 注册用户id*/@NotNull(message = "注册用户id不能为空")private Long registerUserId;/*** 题目id列表*/@NotEmpty(message = "题目id列表不能为空")private String questionIdList;/*** 提交答案列表*/@NotEmpty(message = "提交答案列表不能为空")private String submitAnswerList;/*** 实际答案列表*/@NotEmpty(message = "实际答案列表不能为空")private String answerList;/*** 创建时间*/private Date createTime;private static final long serialVersionUID = 1L;}
解释:如图我这里的开始学习时间字段为空,但是提交接口后没有为空的提示,直接报错了

