前端傳過來數據的時候,要進行校驗,但是大量的校驗很繁瑣,會造成大量的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,就可以對所有已經分組的字段進行校驗了,但是沒有進行分組的字段是不會被校驗的.