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,就可以对所有已经分组的字段进行校验了,但是没有进行分组的字段是不会被校验的.