1. 程式人生 > 實用技巧 >SpringBoot引數自定義校驗

SpringBoot引數自定義校驗

通過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;
    }

}

至此,整個自定義引數的寫法就結束了