springboot中@Valid注解与@Validated注解区别以及全局异常的处理

前端传过来数据的时候,要进行校验,但是大量的校验很繁琐,会造成大量的if else语句的产生,所以@Valid和@Validated很好的解决了这个问题.

首先说一下两个注解的区别:

  1.两者的所属的包是不同的

    @Valid属于javax.validation包下,是jdk给提供的

    @Validated是org.springframework.validation.annotation包下的,是spring提供的

  2.@Validated要比@Valid更加强大

    @Validated在@Valid之上提供了分组功能和验证排序功能

一.处理校验的异常

首先定义一个实体类:

@Data
public class Person {
    @NotEmpty(message = "姓名不能为空")
    private String name;
    @Max(value = 18,message = "年龄不能超过18岁")
    private String age;
    @Max(value = 1, message = "性别只能为0和1: 0=女1=男")
    @Min(value = 0, message = "性别只能为0和1: 0=女1=男")
    private Short sex;
}

然后controller,BindingResult对象,用于获取校验失败情况下的反馈信息:

@RestController
@Slf4j
public class VerifyController {

    @PostMapping(value = "/valid")
    public void verifyValid(@Valid @RequestBody Person person, BindingResult result) {
        log.info("I am verifyValid() method, the request params is: 【{}】", JSON.toJSONString(person));
        if (result.hasErrors()) {
            FieldError fieldError = result.getFieldError();
            if (fieldError != null) {
                log.error("error msg: 【{}】", fieldError.getDefaultMessage());
            }
        }
    }

    @PostMapping(value = "/validated")
    public void verifyValidated(@Validated @RequestBody Person person, BindingResult result) {
        log.info("I am verifyValidated() method, the request params is: 【{}】", JSON.toJSONString(person));
        if (result.hasErrors()) {
            FieldError fieldError = result.getFieldError();
            if (fieldError != null) {
                log.error("error msg: 【{}】", fieldError.getDefaultMessage());
            }
        }
    }
}

此时访问两个controller结果都可以进行校验:

/validated:

2020-01-14 12:32:54.805  INFO 25244 --- [nio-8080-exec-1] com.example.controller.VerifyController  : I am verifyValidated() method, the request params is: 【{"age":"19","name":"","sex":10}】
2020-01-14 12:32:54.806 ERROR 25244 --- [nio-8080-exec-1] com.example.controller.VerifyController  : error msg: 【性别只能为0和1: 0=女1=男】
2020-01-14 12:33:10.638  INFO 25244 --- [nio-8080-exec-2] com.example.controller.VerifyController  : I am verifyValidated() method, the request params is: 【{"age":"19","name":"","sex":1}】
2020-01-14 12:33:10.639 ERROR 25244 --- [nio-8080-exec-2] com.example.controller.VerifyController  : error msg: 【年龄不能超过18岁】
2020-01-14 12:33:18.034  INFO 25244 --- [nio-8080-exec-3] com.example.controller.VerifyController  : I am verifyValidated() method, the request params is: 【{"age":"18","name":"","sex":1}】
2020-01-14 12:33:18.034 ERROR 25244 --- [nio-8080-exec-3] com.example.controller.VerifyController  : error msg: 【姓名不能为空】

/valid:

2020-01-14 12:35:19.151  INFO 25244 --- [nio-8080-exec-5] com.example.controller.VerifyController  : I am verifyValid() method, the request params is: 【{"age":"19","name":"","sex":10}】
2020-01-14 12:35:19.151 ERROR 25244 --- [nio-8080-exec-5] com.example.controller.VerifyController  : error msg: 【姓名不能为空】
2020-01-14 12:35:24.306  INFO 25244 --- [nio-8080-exec-6] com.example.controller.VerifyController  : I am verifyValid() method, the request params is: 【{"age":"19","name":"aa","sex":10}】
2020-01-14 12:35:24.306 ERROR 25244 --- [nio-8080-exec-6] com.example.controller.VerifyController  : error msg: 【年龄不能超过18岁】
2020-01-14 12:35:29.565  INFO 25244 --- [nio-8080-exec-7] com.example.controller.VerifyController  : I am verifyValid() method, the request params is: 【{"age":"18","name":"aa","sex":10}】
2020-01-14 12:35:29.565 ERROR 25244 --- [nio-8080-exec-7] com.example.controller.VerifyController  : error msg: 【性别只能为0和1: 0=女1=男】

在有些时候我们不一定能够使用BindingResult result来处理校验的结果集,在实际的生产环境中,更方便的是获取该异常然后进行返回.

通过观察发现,两者在使用@RequestBody参数注解的情况下,两者抛出的都是MethodArgumentNotValidException异常:

org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public void com.example.controller.VerifyController.verifyValidated(com.example.model.Person) with 3 errors: [Field error in object 'person' on field 'age': rejected value [19]; codes [Max.person.age,Max.age,Max.java.lang.String,Max]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [person.age,age]; arguments []; default message [age],18]; default message [年龄不能超过18岁]] [Field error in object 'person' on field 'name': rejected value []; codes [NotEmpty.person.name,NotEmpty.name,NotEmpty.java.lang.String,NotEmpty]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [person.name,name]; arguments []; default message [name]]; default message [姓名不能为空]] [Field error in object 'person' on field 'sex': rejected value [10]; codes [Max.person.sex,Max.sex,Max.java.lang.Short,Max]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [person.sex,sex]; arguments []; default message [sex],1]; default message [性别只能为0和1: 0=女1=男]] 
    at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.resolveArgument(RequestResponseBodyMethodProcessor.java:139)
    at org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:121)
    at org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:167)
    at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:134)
    at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:106)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:888)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:793)
    at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1040)
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943)
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)
    at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909)

为了统一抓取异常,首先定义一个全局异常类:

@ControllerAdvice
@ResponseBody
public class GlobleExceptionHandler {
    /**
     * 要拦截的异常Exception
     */
    @ExceptionHandler(value = Exception.class)
    public Result<String> exceptionHandler(Exception e) {
        
        if (e instanceof BindException) {
            e.printStackTrace();  //将异常打印出来
            BindException ex = (BindException) e;
            List<ObjectError> allErrors = ex.getAllErrors();
            ObjectError objectError = allErrors.get(0);
            String ms = objectError.getDefaultMessage();
            return Result.error(CodeMsg.BIND_ERROR.fillArgs(ms));
        } else {
            e.printStackTrace();
            return Result.error(CodeMsg.SERVER_ERROR);
        }
    }
}

返回值的Result是统一封装了返回给前端的json数据,具体的实现可以在我上一篇博客中查看:

https://www.cnblogs.com/zhiweiXiaomu/p/12190353.html

但是如果不在controller接口中加@RequestBody注解,两者抛出的则是BindException:

 

org.springframework.validation.BindException: org.springframework.validation.BeanPropertyBindingResult: 3 errors

 

这里提醒一下各位小伙伴,在controller接口中加不加@ResquestBody注解,抛出的校验异常是不一样的.各位在抓取异常的时候需要注意一下.

二.@Valid和@Validated两者的区别

  两者的主要区别就是关于分组和分组排序了.@Validated中可以进行分组排序

  首先定义两个接口:  

  

public interface First {
}

public interface Second {
}

 

 

 

  这俩接口是用来分组的.

  实体类如下:

@Data
public class Person {
    @NotEmpty(groups = First.class, message = "姓名不能为空")
    private String name;
    @Max(value = 18, groups = Second.class,message = "年龄不能超过18岁")
    private String age;
    @Max(value = 1, message = "性别只能为0和1: 0=女1=男")
    @Min(value = 0, message = "性别只能为0和1: 0=女1=男")
    private Short sex;
}

 

  然后controller(与上面一样,两个都没有添加分组),多次校验下我们会发现不管是 @Valid 注解还是 @Validated 注解都只会校验没有添加 groups 属性的实体类字段 (此处只校验了 sex 字段)

  也就是说,在实体类中,添加分组的两个字段,name,和age在校验中失效了.

  因为@Valid注解中没有关于分组的参数,所以在@Validated中加入注解,变成下面这样:

@RestController
@Slf4j
public class VerifyController {

    @PostMapping(value = "/valid")
    public void verifyValid(@Valid @RequestBody Person person, BindingResult result) {
        log.info("I am verifyValid() method, the request params is: 【{}】", JSON.toJSONString(person));
        if (result.hasErrors()) {
            FieldError fieldError = result.getFieldError();
            if (fieldError != null) {
                log.error("error msg: 【{}】", fieldError.getDefaultMessage());
            }
        }
    }
    @PostMapping(value = "/validated")
    public void verifyValidated(@Validated(value = First.class) @RequestBody Person person, BindingResult result) {
        log.info("I am verifyValidated() method, the request params is: 【{}】", JSON.toJSONString(person));
        if (result.hasErrors()) {
            FieldError fieldError = result.getFieldError();
            if (fieldError != null) {
                log.error("error msg: 【{}】", fieldError.getDefaultMessage());
            }
        }
    }
}

 

  此时,@Validated则可以校验对应有First分组的name字段了,但是别的字段,也就是说除了加了First分组的name字段,别的字段都不能进行校验.

         那么怎么办呢,因为value是Clazz[]数组格式的,所以可以这样:

  value={First.class,Second.class}

  也可以进行分组排序:

  定义一个分组排序接口:

  

@GroupSequence({First.class,Second.class})
public interface Group {
}

 

   然后controller中    value=Group.class,就可以对所有已经分组的字段进行校验了,但是没有进行分组的字段是不会被校验的.

  

 

 

  

posted @ 2020-01-14 13:16  萧暮  阅读(8518)  评论(0编辑  收藏  举报