1.前言
在项目里面,我们需要对前端传入的参数做一个简单的简单的校验,避免出现脏数据和业务逻辑错误。如果每个接口单独写校验逻辑的话,我们需要在controller层做逻辑判断。参数较少时,还勉强能够接受,如果参数和接口较多,无形中加重了工作量,也多了很多重复代码。所以引入注解式参数校验很有必要。
2.引入方式
2.1引入jar包
本文是基于springboot来实现参数校验,引入方式很简单,在pom中引入spring-boot-starter-validation即可。
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId></dependency>
2.2全局异常捕获
一般情况下,我们使用参数校验都需要返回异常信息,搭配全局异常捕获食用效果最佳。这里我就不介绍异常捕获的工作原理了,简单贴一下代码以供参考。先不需要考虑这的捕获器1、2、3、4是干什么的,下面会介绍作用。
@Slf4j@RestControllerAdvicepublicclassGlobalExceptionHandler{//捕获器1@ExceptionHandler(value ={MissingServletRequestParameterException.class})publicResponseVO<String>handleMissingServletRequestParameterException(MissingServletRequestParameterException ex){if(log.isErrorEnabled()){
log.error(ex.getMessage(), ex);}returnResponseVO.error(ResponseConstant.ERROR_CODE,String.format("缺少必要参数[%s]", ex.getParameterName()),"");}//捕获器2@ExceptionHandler(value ={MethodArgumentNotValidException.class,BindException.class})publicResponseVO<String>handleMethodArgumentNotValidException(MethodArgumentNotValidException ex){if(log.isErrorEnabled()){
log.error(ex.getMessage(), ex);}BindingResult result = ex.getBindingResult();FieldError error = result.getFieldError();returnResponseVO.error(ResponseConstant.ERROR_CODE,null== error ?ResponseConstant.ERROR_MESSAGE : error.getDefaultMessage(),"");}//捕获器3@ExceptionHandler(value ={BindException.class})publicResponseVO<String>handleBindException(BindException ex){if(log.isErrorEnabled()){
log.error(ex.getMessage(), ex);}BindingResult result = ex.getBindingResult();FieldError error = result.getFieldError();returnResponseVO.error(ResponseConstant.ERROR_CODE,null== error ?ResponseConstant.ERROR_MESSAGE : error.getDefaultMessage(),"");}//捕获器4@ExceptionHandler(value ={ConstraintViolationException.class})publicResponseVO<String>handleConstraintViolationException(ConstraintViolationException ex){if(log.isErrorEnabled()){
log.error(ex.getMessage(), ex);}Optional<ConstraintViolation<?>> first = ex.getConstraintViolations().stream().findFirst();returnResponseVO.error(ResponseConstant.ERROR_CODE, first.isPresent()? first.get().getMessage():ResponseConstant.ERROR_MESSAGE,"");}//其他所有异常捕获器@ExceptionHandler(Exception.class)publicResponseVO<String>otherErrorDispose(Exception e){// 打印错误日志
log.error("错误代码({}),错误信息({})",ResponseConstant.ERROR_CODE, e.getMessage());
e.printStackTrace();returnResponseVO.error(ResponseConstant.ERROR_CODE,ResponseConstant.ERROR_MESSAGE, e.getMessage());}}
自定义异常返回和自定义常量
//自定义接口响应类@DatapublicclassResponseVO<T>implementsSerializable{// 状态码: 0-成功,其他-失败privatefinalInteger code;// 返回信息privatefinalString message;//返回值privatefinalT data;//是否成功privatefinalBoolean success;// 成功返回publicstatic<T>ResponseVO<T>success(T data){returnnewResponseVO<>(data);}// 失败返回publicstatic<T>ResponseVO<T>error(Integer code,String message,T data){returnnewResponseVO<>(code, message, data);}publicResponseVO(T data){this.code =ResponseConstant.SUCCESS_CODE;this.message =ResponseConstant.OK;this.data = data;this.success =true;}publicResponseVO(Integer code,String message,T data){this.code = code;this.message = message;this.data = data;this.success = code ==ResponseConstant.SUCCESS_CODE;}}//自定义常量类publicclassResponseConstant{publicstaticfinalString OK ="OK";publicstaticfinalString ERROR ="error";publicstaticfinalint SUCCESS_CODE =200;publicstaticfinalint ERROR_CODE =500;publicstaticfinalString ERROR_MESSAGE ="操作失败!!";}
3.可能会遇到的问题
这里我就不介绍使用方式了,网上有很多详细的案例,包括每个注解的作用介绍,分组校验和自定义校验的使用方法(话说我自己都没有用过,只用过简单的注解)。
3.1注解不生效
注解不生效的情况有很多,主要参考的解决思路:jar包冲突、加错注解、少了关键注解。
(1)jar包冲突可能是引入的时候引入了多个版本的jar包,注意检查pom,在springboot中只需要安装上述方式引入即可,无需再引入其他validator相关jar包。
(2)加错注解,主要是看你引入的注解是不是在下述这个路径下的。
(3)少了关键注解
//@Validated@RestController@RequestMapping("test")publicclassTestController{@GetMapping("test1")publicResponseVO<Integer>test2(@NotNull(message ="最小值不能为空")Integer minNum,@NotNull(message ="最大值不能为空")@Min(value =10,message ="参数必须大于10")Integer maxNum){returnResponseVO.success(11);}}
如上明明写了@NotNull,也确认了引入的注解是对的,但是就是不返回错误信息。像这种参数没有放在一个对象中,而是直接写在接口上的情况,需要在类上加@Validated注解,否则不会生效。
@Validated@RestController@RequestMapping("test")publicclassTestController{@GetMapping("test3")publicResponseVO<Integer>test3(BlacklistPageParamVO vo){returnResponseVO.success(11);}@PostMapping("test4")publicResponseVO<Integer>test4(@Valid@RequestBodyBlacklistPageParamVO vo){returnResponseVO.success(11);}}@DatapublicclassBlacklistPageParamVO{@NotBlank(message ="日期不能为空")privateString date;@NotBlank(message ="日期2不能为空")privateString date2;@Valid@NotNull(message ="内部对象不能为空")privateInnerVO innerVO;}@DatapublicclassInnerVO{@NotNull(message ="num1不能为空")privateInteger num1;@NotNull(message ="num2不能为空")privateInteger num2;}
第二种情况就是,我的校验字段在一个对象里面,这个时候需要在对象前面加上@Valid注解,所以test3的检验不会生效。这里扩展一下如果一个对象中还有另一个对象,且内部的对象也有需要检验的字段,需要给这个内部对象也加上@Valid注解才会生效。
3.2注解生效但返回的不是自定义信息
@Validated@RestController@RequestMapping("test")publicclassTestController{@GetMapping("test1")publicResponseVO<Integer>test1(@NotNull(message ="最小值不能为空")@RequestParamInteger minNum,@NotNull(message ="最大值不能为空")@RequestParamInteger maxNum){returnResponseVO.success(11);}}
像上面这种情况如果在参数前加了@RequestParam表示参数必传,可以理解为作用和@NotNull是一样的。此时调用接口时,忘记传参数minNum了,我期待返回的是“最小值不能为空”,但是实际上返回的是“Required Integer parameter ‘minNum’ is not present”,且控制台打印的错误日志如下:
从错误名称,我们就可以看出,这是由于加了@RequestParam注解导致,让你传,你不传,所以报了这个错。这个错误对应的就是我们上文中的捕获器1。而且由此可以看出MissingServletRequestParameterException异常是比validation的异常优先级高的。
4.几个异常捕获器的作用
4.1捕获器1
MissingServletRequestParameterException
加了@RequestParam注解,但是接口调用时没有传指定的参数(注意:是没有传,而不是传了,但是值是null)。
4.2捕获器2
MethodArgumentNotValidException
经过测试,当校验的参数放在对象中,接口的请求方式是post请求,用@Valid @RequestBody方式接受参数时,如果报错,会被该捕获器捕获。
4.3捕获器3
BindException
经过测试,当校验参数写在类中,接口请求方式是get请求时,报错会被该捕获器捕获。
4.4捕获器4
ConstraintViolationException
传了值,但是不符合要求。@NotNull(message = “最大值不能为空”) @Min(value = 10,message = “参数必须大于10”),要求传非null值,且值必须大于10,否则会返回错误信息。经过测试,当校验参数直接写在接口上,而不是写在类中,报错会被该捕获器捕获。
4个捕获器对应4种不同的场景。
版权归原作者 一只小T 所有, 如有侵权,请联系我们删除。