引入maven依賴(可選)
如果我們的項目使用了Spring Boot,hibernate validator框架已經集成在 spring-boot-starter-web中,所以無需再添加其他依賴。如果不是Spring Boot項目,則需要添加如下依賴:
1 <dependency> 2 <groupId>org.hibernate.validator</groupId> 3 <artifactId>hibernate-validator</artifactId> 4 <version>6.0.8.Final</version> 5 </dependency>
常用注解介紹
注解 | 作用類型 | 來源 | 說明 |
@Null | 任何類型 | 屬性必須為null | |
@NotNull | 任何類型 | 屬性不能為null | |
@NotEmpty | 集合 | hibernate validator擴展注解 | 集合不能為null,且size大於0 |
@NotBlank | 字符串、字符 | hibernate validator擴展注解 | 字符類不能為null,且去掉空格之后長度大於0 |
@AssertTrue | Boolean、boolean | 布爾屬性必須是true | |
@Min | 數字類型(原子和包裝) | 限定數字的最小值(整型) | |
@Max | 同@Min | 限定數字的最大值(整型) | |
@DecimalMin | 同@Min | 限定數字的最小值(字符串,可以是小數) | |
@DecimalMax | 同@Min | 限定數字的最大值(字符串,可以是小數) | |
@Range | 數字類型(原子和包裝) | hibernate validator擴展注解 | 限定數字范圍(長整型) |
@Length(min=,max=) | 字符串 | hibernate validator擴展注解 | 限定字符串長度 |
@Size | 集合 | 限定集合大小 | |
@Past | 時間、日期 | 必須是一個過去的時間或日期 | |
@Future | 時期、時間 | 必須是一個未來的時間或日期 | |
字符串 | hibernate validator擴展注解 | 必須是一個郵箱格式 | |
@Pattern | 字符串、字符 | 正則匹配字符串 |
單參數校驗
controller類上必須添加@Validated注解,如下所示:
1 @RestController 2 @RequestMapping("/user") 3 @Validated // 需要添加的注解 4 public class UserController { 5 // do something 6 }
方法參數前面加上注解即可, 如下所示:
1 public Result deleteUser(@NotNull(message = "id不能為空") Long id) { 2 // do something 3 }
對象參數校驗
對象參數校驗使用時,需要先在對象的校驗屬性上添加注解,然后在Controller方法的對象參數前添加@Validated注解,如下所示:
1 public Result addUser(@RequestBody @Validated UserDto userDto) { 2 // do something 3 } 4 5 public class UserDto { 6 @NotBlank(message="名稱不能為空") 7 private String name; 8 9 @NotNull(message="年齡不能為空") 10 private Integer age; 11 12 …… 13 }
嵌套對象校驗
如果需要校驗的參數對象中還嵌套有一個對象屬性,而該嵌套的對象屬性也需要校驗,那么就需要在該對象屬性上增加@Valid注解。
1 public class UserDto { 2 @NotNull 3 private Long id; 4 5 @NotBlank 6 private String name; 7 8 @NotNull 9 private Integer age; 10 11 @Valid 12 private Phone phone; 13 14 …… 15 } 16 17 public class Phone { 18 @NotBlank 19 private String type; 20 21 @NotBlank 22 private String phoneNum; 23 }
分組校驗
在對象參數校驗場景下,有一種特殊場景,同一個參數對象在不同的場景下有不同的校驗規則。比如,在創建對象時不需要傳入id字段(id字段是主鍵,由系統生成,不由用戶指定),但是在修改對象時就必須要傳入id字段。在這樣的場景下就需要對注解進行分組。
1)組件默認有個分組Default.class, 所以我們可以再創建一個分組例如UpdateAction.class,如下所示:
1 public interface UpdateAction { 2 }
2)在參數類中需要校驗的屬性上,在注解中添加groups屬性(表示Default.class默認情況下會校驗name,age參數,而在UpdateAction.class情況下會校驗id參數),如下所示:
1 public class UserDto { 2 @NotNull(groups = {UpdateAction.class}, message = "id不能為空") 3 private Long id; 4 5 @NotBlank 6 private String name; 7 8 @NotNull 9 private Integer age; 10 11 …… 12 }
3)在controller的方法中,在@Validated注解里指定哪種場景即可(沒有指定就代表采用Default.class,采用其他分組則需要顯示指定)。如下代碼便表示在addUser()接口中按照默認情況進行參數校驗,在updateUser()接口中按照默認情況和UpdateAction分組對參數進行共同校驗。
1 public Result addUser(@Validated UserDto userDto) { 2 // do something 3 } 4 5 public Result updateUser(@Validated({Default.class, UpdateAction.class}) UserDto userDto) { 6 // do something 7 }
枚舉校驗
枚舉校驗hibernate validator並不支持。需要自己擴展實現。
1) 定義EnumChck注解,如下所示:
1 @Documented 2 @Constraint(validatedBy = EnumConstraintValidator.class) 3 @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE }) 4 @Retention(RUNTIME) 5 @Repeatable(EnumCheck.List.class) 6 public @interface EnumCheck { 7 String message() default "{javax.validation.constraints.EnumCheck.message}"; 8 9 Class<?>[] groups() default { }; 10 11 Class<? extends Payload>[] payload() default { }; 12 13 /** 14 * 枚舉類 15 * 16 */ 17 Class<? extends EnumValidator> clazz(); 18 19 /** 20 * 調用的方法名稱 21 */ 22 String method() default "getValue"; 23 24 @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE }) 25 @Retention(RUNTIME) 26 @Documented 27 @interface List { 28 EnumCheck[] value(); 29 } 30 }
2) 定義EnumValidator接口,讓需要校驗的枚舉類實現其接口的getValue()方法,如下所示:
1 public interface EnumValidator { 2 Object getValue(); 3 }
3)自定義實現EnumConstraintValidator,需要實現ConstraintValidator接口,如下所示:
1 public class EnumConstraintValidator implements ConstraintValidator<EnumCheck, Object> { 2 /** 3 * 注解對象 4 */ 5 private EnumCheck annotation; 6 7 /** 8 * 初始化方法 9 * 10 * @param constraintAnnotation 注解對象 11 */ 12 @Override 13 public void initialize(EnumCheck constraintAnnotation) { 14 this.annotation = constraintAnnotation; 15 } 16 17 @Override 18 public boolean isValid(Object value, ConstraintValidatorContext context) { 19 if (Objects.isNull(value)) { 20 return false; 21 } 22 23 Object[] enumConstants = annotation.clazz().getEnumConstants(); 24 try { 25 Method method = annotation.clazz().getMethod(annotation.method()); 26 for (Object enumConstant : enumConstants) { 27 if (value.equals(method.invoke(enumConstant))) { 28 return true; 29 } 30 } 31 } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { 32 throw new RuntimeException(e); 33 } 34 35 return false; 36 } 37 }
4) 具體使用步驟如下:
具體枚舉類實現上面定義的EnumValidator接口:
1 public enum RefNodeType implements EnumValidator { 2 PROJECT("project", "項目"), 3 FIELD("field", "變量"), 4 FUNC("func", "函數"); 5 6 private final String code; 7 private final String label; 8 9 RefNodeType(String code, String label) { 10 this.code = code; 11 this.label = label; 12 } 13 14 public String getCode() { 15 return code; 16 } 17 18 public String getLabel() { 19 return label; 20 } 21 22 @Override // 需要實現getValue方法 23 public Object getValue() { 24 return code; 25 } 26 }
參數校驗加上@EnumCheck枚舉校驗注解,如下所示:
1 public class TestDto { 2 @NotBlank(message = "變量類型不能為空") 3 @EnumCheck(clazz = RefNodeType.class, message = "變量類型不合法") // 加上枚舉校驗注解 4 private String sourceType; 5 }
正則通用校驗
1)定義RegRule接口,將需要用到的正則表達式定義為接口屬性,如下所示:
1 public interface RegRule { 2 String MOBILE = "^(((13[0-9])|(14[579])|(15([0-3]|[5-9]))|(16[6])|(17[0135678])|(18[0-9])|(19[89]))\\d{8})$"; 3 }
2)對象屬性加上@Pattern注解,如下所示:
1 public class UserDto { 2 @Pattern(regexp = RegRule.MOBILE, message = "手機號格式不正確") 3 private String mobile; 4 }
各類異常捕獲處理
參數校驗失敗后會拋出異常,我們只需要在全局異常處理類中捕獲參數校驗的失敗異常,然后將錯誤消息添加到返回值中即可。捕獲異常的方法如下所示,返回值Result是我們系統自定義的返回值類。
1 @RestControllerAdvice 2 public class GlobalExceptionHandler { 3 /** 4 * 全局異常處理 5 */ 6 @ExceptionHandler(Exception.class) 7 public Result handleException(Exception ex, HttpServletRequest request, HttpServletResponse response) { 8 // do something 9 } 10 }
1)缺少參數拋出的異常是MissingServletRequestParameterException,如下所示:
1 if (e instanceof MissingServletRequestParameterException){ 2 Result result = Result.buildErrorResult(ErrorCodeEnum.PARAM_ILLEGAL); 3 String msg = MessageFormat.format("缺少參數{0}", ((MissingServletRequestParameterException) e).getParameterName()); 4 result.setMessage(msg); 5 return result; 6 }
2)單參數校驗失敗后拋出的異常是ConstraintViolationException,如下所示:
1 if (e instanceof ConstraintViolationException){ 2 Result result = Result.buildErrorResult(ErrorCodeEnum.PARAM_ILLEGAL); 3 Set<ConstraintViolation<?>> sets = ((ConstraintViolationException) e).getConstraintViolations(); 4 if(CollectionUtils.isNotEmpty(sets)){ 5 StringBuilder sb = new StringBuilder(); 6 sets.forEach(error -> { 7 if (error instanceof FieldError) { 8 sb.append(((FieldError)error).getField()).append(":"); 9 } 10 sb.append(error.getMessage()).append(";"); 11 }); 12 String msg = sb.toString(); 13 msg = StringUtils.substring(msg, 0, msg.length() -1); 14 result.setMessage(msg); 15 } 16 return result; 17 }
3)get請求的對象參數校驗失敗后拋出的異常是BindException,如下所示:
1 if (e instanceof BindException){ 2 Result result = Result.buildErrorResult(ErrorCodeEnum.PARAM_ILLEGAL); 3 List<ObjectError> errors = ((BindException) e).getBindingResult().getAllErrors(); 4 String msg = getValidExceptionMsg(errors); 5 if (StringUtils.isNotBlank(msg)){ 6 result.setMessage(msg); 7 } 8 9 return result; 10 } 11 12 private String getValidExceptionMsg(List<ObjectError> errors) { 13 if(CollectionUtils.isNotEmpty(errors)){ 14 StringBuilder sb = new StringBuilder(); 15 errors.forEach(error -> { 16 if (error instanceof FieldError) { 17 sb.append(((FieldError)error).getField()).append(":"); 18 } 19 sb.append(error.getDefaultMessage()).append(";"); 20 }); 21 String msg = sb.toString(); 22 msg = StringUtils.substring(msg, 0, msg.length() -1); 23 return msg; 24 } 25 return null; 26 }
4)post請求的對象參數校驗失敗后拋出的異常是MethodArgumentNotValidException,如下所示:
1 if (e instanceof MethodArgumentNotValidException){ 2 Result result = Result.buildErrorResult(ErrorCodeEnum.PARAM_ILLEGAL); 3 List<ObjectError> errors = ((MethodArgumentNotValidException) e).getBindingResult().getAllErrors(); 4 String msg = getValidExceptionMsg(errors); 5 if (StringUtils.isNotBlank(msg)){ 6 result.setMessage(msg); 7 } 8 9 return result; 10 }