https://www.jianshu.com/p/89a675b7c900
在日常開發寫rest接口時,接口參數校驗這一部分是必須的,但是如果全部用代碼去做,顯得十分麻煩,spring也提供了這部分功能,本文來探究一下如何實現
1.配置
spring-boot-starter-web包自動依賴hibernate-validator,不用再重復引入,直接開搞
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-validator</artifactId> <version>5.3.1.Final</version> </dependency>
聲明一個bean注冊到spring容器,這個bean是一個容器后處理器,會把校驗的邏輯通過AOP織入有@Validated注解的class,具體可以看這個類的源碼
這一步在springboot其實也不用做,ValidationAutoConfiguration這個配置類自動幫我們做了
@Bean public MethodValidationPostProcessor methodValidationPostProcessor(){ return new MethodValidationPostProcessor(); }
驗證不通過會產生異常,因為我們項目提供rest接口,所以通過全局捕獲異常,然后轉換為json給前台
@ControllerAdvice public class GlobalExceptionHandler { /** * 用來處理bean validation異常 * @param ex * @return */ @ExceptionHandler(ConstraintViolationException.class) @ResponseBody public WebResult resolveConstraintViolationException(ConstraintViolationException ex){ WebResult errorWebResult = new WebResult(WebResult.FAILED); Set<ConstraintViolation<?>> constraintViolations = ex.getConstraintViolations(); if(!CollectionUtils.isEmpty(constraintViolations)){ StringBuilder msgBuilder = new StringBuilder(); for(ConstraintViolation constraintViolation :constraintViolations){ msgBuilder.append(constraintViolation.getMessage()).append(","); } String errorMessage = msgBuilder.toString(); if(errorMessage.length()>1){ errorMessage = errorMessage.substring(0,errorMessage.length()-1); } errorWebResult.setInfo(errorMessage); return errorWebResult; } errorWebResult.setInfo(ex.getMessage()); return errorWebResult; } @ExceptionHandler(MethodArgumentNotValidException.class) @ResponseBody public WebResult resolveMethodArgumentNotValidException(MethodArgumentNotValidException ex){ WebResult errorWebResult = new WebResult(WebResult.FAILED); List<ObjectError> objectErrors = ex.getBindingResult().getAllErrors(); if(!CollectionUtils.isEmpty(objectErrors)) { StringBuilder msgBuilder = new StringBuilder(); for (ObjectError objectError : objectErrors) { msgBuilder.append(objectError.getDefaultMessage()).append(","); } String errorMessage = msgBuilder.toString(); if (errorMessage.length() > 1) { errorMessage = errorMessage.substring(0, errorMessage.length() - 1); } errorWebResult.setInfo(errorMessage); return errorWebResult; } errorWebResult.setInfo(ex.getMessage()); return errorWebResult; } }
這兩個異常分別對應校驗的兩種使用方式
- 在方法里面校驗
- 在bean對象里面校驗
經過測試,以上兩種形式的數據驗證不僅僅對controller層有用,在service層也行,只要這個類在spring ioc容器里面
2.使用
2.1常用校驗注解
@AssertFalse 校驗false @AssertTrue 校驗true @DecimalMax(value=,inclusive=) 小於等於value, inclusive=true,是小於等於 @DecimalMin(value=,inclusive=) 與上類似 @Max(value=) 小於等於value @Min(value=) 大於等於value @NotNull 檢查Null @Past 檢查日期 @Pattern(regex=,flag=) 正則 @Size(min=, max=) 字符串,集合,map限制大小 @Valid 對po實體類進行校驗
2.2在方法參數上使用
@Controller
@Validated
public class ValidationController { @GetMapping("/validate1") @ResponseBody public String validate1( @Size(min = 1,max = 10,message = "姓名長度必須為1到10")@RequestParam("name") String name, @Min(value = 10,message = "年齡最小為10")@Max(value = 100,message = "年齡最大為100") @RequestParam("age") Integer age, @Future @RequestParam("birth")@DateTimeFormat(pattern = "yyyy-MM-dd hh:mm:ss") Date birth){ return "validate1"; } }
注意類名需要加注解@Validated
校驗失敗會拋出ConstraintViolationException異常
然后我們在全局異常捕獲類捕獲這個異常,返回給前台對應的錯誤json
2.3在bean內屬性上使用
給model類增加校驗注解
public class User { @Size(min = 1,max = 10,message = "姓名長度必須為1到10") private String name; @NotEmpty private String firstName; @Min(value = 10,message = "年齡最小為10")@Max(value = 100,message = "年齡最大為100") private Integer age; @Future @JSONField(format="yyyy-MM-dd HH:mm:ss") private Date birth; ...getter setter }
在controller對應User實體前增加@Valid注解
@PostMapping("/validate2") @ResponseBody public User validate2(@Valid @RequestBody User user){ return user; }
3.擴展
除了默認提供的校驗注解外,我們可以定義自己的校驗注解
3.1.創建約束注解類
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER }) @Retention(RUNTIME) @Documented @Constraint(validatedBy = { HandsomeBoyValidator.class}) public @interface HandsomeBoy { String message() default "盛超傑最帥"; String name(); Class<?>[] groups() default { }; Class<? extends Payload>[] payload() default {}; }
注意:message用於顯示錯誤信息這個字段是必須的,groups和payload也是必須的
@Constraint(validatedBy = { HandsomeBoyValidator.class})用來指定處理這個注解邏輯的類
一開始寫了這個自定義注解和驗證類,發現沒有生效,最后發現是@Constraint這個注解里的類沒有配置,還跟了很多源碼,蛋疼,總的來講,這個配置還是挺方便的
3.2.創建驗證器類
public class HandsomeBoyValidator implements ConstraintValidator<HandsomeBoy, User> { private String name; /** * 用於初始化注解上的值到這個validator * @param constraintAnnotation */ @Override public void initialize(HandsomeBoy constraintAnnotation) { name =constraintAnnotation.name(); } /** * 具體的校驗邏輯 * @param value * @param context * @return */ @Override public boolean isValid(User value, ConstraintValidatorContext context) { return name ==null || name.equals(value.getName()); } }
這邊的功能是user類里面的name字段必須和配置的一樣,否則輸出一個事實
3.3. 測試
@PostMapping("/validate3") @ResponseBody public User validate3(@Valid @HandsomeBoy(name = "scj",message = "盛超傑第二帥") @RequestBody User user){ return user; }
如果驗證不通過,會輸出盛超傑第二帥,全局異常處理器不要忘記配置
4.demo源碼下載
小禮物走一走,來簡書關注我