通過Hibernate的可以對一些基礎數據進行校驗,但是在真實的業務場景下,我們的驗證是針對復雜的業務邏輯進行驗證而不單單是對基礎數據的驗證。舉個例子,用戶在注冊的時候,用戶要輸入兩次密碼,一次是原密碼,一次是確認密碼,兩次密碼一致才允許用戶進行注冊,那么這種需要比較兩個字段相等的驗證如何來寫?
1、自定義元注解
@Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) public @interface PasswordEqual { String message() default "passwords are not equal"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; }
其中,@Documented、@Retention(RetentionPolicy.RUNTIME)、@Target({ElementType.TYPE}) 這三個注解是元注解。這只是一個注解,並沒有業務邏輯,業務邏輯的編寫需要一個關聯類
2、編寫關聯類
關聯類必須要實現 ConstraintValidator<A, T> 接口, 這個接口接收的兩個泛型的參數,其中第一個參數是注解的類型,這里就是 PasswordEqual,第二個參數是自定義注解所修飾的目標的類型,就是說我們的自定義注解修飾的是哪個 DTO,這里傳入的就是那個 DTO 的類型,例如我們要將該注解用在 PersonDTO 上,那么這里傳入的就是 PersonDTO。
@PasswordEqual 修飾的 DTO:
@Builder @Getter @PasswordEqual public class PersonDTO { @Length(min = 3, max = 8, message = "姓名長度必須在3-8之間") private String name; @Max(120) private Integer age; @Valid private SchoolDTO schoolDTO; }
關聯類(PasswordValidator)
/** * 自定義注解 @PasswordEqual 的關聯類, 用於校驗兩次輸入的面是否一致 * * ConstraintValidator 接口的第一個參數傳入的是自定義注解的類型, * 第二個參數傳入的是自定義注解修飾的目標的類型 * * 例如: * @PasswordEqual * public class PersonDTO { * private String name; * } * 那么第二個參數傳入的就是 PersonDTO * * 例如: * public class PersonDTO { * @PasswordEqual * private String name; * } * 那么第二個參數傳入的就是 String */ public class PasswordValidator implements ConstraintValidator<PasswordEqual, PersonDTO> { // 這里必須要實現 isValid(),校驗邏輯就是這里實現 @Override public boolean isValid(PersonDTO personDTO, ConstraintValidatorContext constraintValidatorContext) { String password1 = personDTO.getPassword1(); String password2 = personDTO.getPassword2(); if (!StringUtils.isBlank(password1) && !StringUtils.isBlank(password2)) { return password1.equals(password2); } return false; } }
3、將自定義注解和關聯類關聯起來
在自定義注解上添加 @Constrain(validatedBy = PasswordValidator.class) 將他們關聯起來,validatedBy 后面的值就是關聯類名;這里的 validatedBy 后面是可以跟一個數組的,也就是說這里可以關聯多個類
@Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) @Constraint(validatedBy = PasswordValidator.class) public @interface PasswordEqual { String message() default "passwords are not equal"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; }
到此,一個自定義的注解就可以使用了,但是在具體業務中,可能會在很多時候需要注解能傳入參數的,類似 @Range(min = 1, max = 10, message = "長度不能只能在1-10之間") 這種的,那么就需要在自定義注解中添加這兩個參數變量用於接收,然后在通過關聯類進行邏輯處理即可。
4、實現接收參數的自定義注解
4.1 增加參數的自定義注解
@Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) @Constraint(validatedBy = PasswordValidator.class) public @interface PasswordEqual { // 注意在注解中只能使用基本類型不能使用包裝類型 int min() default 1; int max() default 10; String message() default "passwords are not equal"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; // 關聯類 }
注意:在自定義注解中只能使用基本類型不能使用包裝類型,也就是只能使用 int 、boolean 這種格式的,而不能使用 Integer 和 Boolean 這種格式
4.2 關聯類接收參數,並進行邏輯處理
關聯類要獲取參數就必須重新接口中的 initialize() 這個方法
/** * 自定義注解 @PasswordEqual 的關聯類, 用於校驗兩次輸入的面是否一致 * <p> * ConstraintValidator 接口的第一個參數傳入的是自定義注解的類型, * 第二個參數傳入的是自定義注解修飾的目標的類型 * <p> * 例如: * * @PasswordEqual public class PersonDTO { * private String name; * } * 那么第二個參數傳入的就是 PersonDTO * <p> * 例如: * public class PersonDTO { * @PasswordEqual private String name; * } * 那么第二個參數傳入的就是 String */ public class PasswordValidator implements ConstraintValidator<PasswordEqual, PersonDTO> { // 用於接收自定義注解中的參數 private int min; private int max; /** * 如果要實現接收參數,則必須要重寫這個方法 * * @param constraintAnnotation 通過該參數的方法可以獲取到自定義注解中的參數 */ @Override public void initialize(PasswordEqual constraintAnnotation) { this.min = constraintAnnotation.min(); this.max = constraintAnnotation.max(); } /** * 要將自定義注解和關聯類關聯起來必須要實現 isValid(),校驗邏輯就是這里實現 * * @param personDTO 自定義注解修飾目標的類型 * @param constraintValidatorContext 關聯類與注解 * @return 是否合法 */ @Override public boolean isValid(PersonDTO personDTO, ConstraintValidatorContext constraintValidatorContext) { String password1 = personDTO.getPassword1(); String password2 = personDTO.getPassword2(); if (password1.length() < this.min || password2.length() > this.max) { return false; } if (!StringUtils.isBlank(password1) && !StringUtils.isBlank(password2)) { return password1.equals(password2); } return false; } }
至此,整個自定義參數的寫法就結束了