枚舉-注解校驗(2)


java validation內沒有對枚舉的校驗工具,但是離散的枚舉值校驗確實是有必要的,這里列兩種枚舉的校驗方法,實際大同小異。首先,javax.validation包是提供了方便的自定義校驗的入口的,就是javax.validation.ConstraintValidator。
1. 對離散值(非枚舉)的校驗
若離散的值尚未形成枚舉,這種校驗反而好做一點,因為無需引入反射這種黑魔法。
校驗注解

package com.springboot.study.tests.annotation;

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;

/**
 * @Author: guodong
 * @Date: 2021/7/24 10:49
 * @Version: 1.0
 * @Description:
 */
@Documented
@Constraint(validatedBy = {EnumStringValidator.class})
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface EnumValidateAnnotation {

    /**
     * 校驗報錯信息
     * @return
     */
    String message() default "";

    /**
     * 校驗分組
     * @return
     */
    Class<?>[] groups() default {};

    /**
     * 附件 用於擴展
     * @return
     */
    Class<? extends Payload>[] payload() default {};

    /**
     * 允許的枚舉值,所有類型轉成String 存儲
     * @return
     */
    String[] enums() default {};

}

校驗實現 

package com.springboot.study.tests.annotation;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.Arrays;
import java.util.List;

/**
 * @Author: guodong
 * @Date: 2021/7/24 12:51
 * @Version: 1.0
 * @Description:
 */
public class EnumStringValidator implements ConstraintValidator<EnumValidateAnnotation,String> {

    private List<String> enumStrings;

    @Override
    public void initialize(EnumValidateAnnotation constraintAnnotation) {
        enumStrings = Arrays.asList(constraintAnnotation.enums());
    }

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        if (value == null) {
            return false;
        }
        // 對於Integer,將其轉化為String 后比較
        return enumStrings.contains(value);
    }
}

使用示例

package com.springboot.study.tests.annotation;

import javax.validation.constraints.NotNull;

/**
 * @Author: guodong
 * @Date: 2021/7/24 10:34
 * @Version: 1.0
 * @Description:
 */
public class Student {

    /**
     * 來源
     */
    @NotNull(message = "請求來源不能為空")
    // 這里將枚舉值FromEnum的String表示放入注解的enums中
    // 如果是離散值的話,更加合適,因為不用關心枚舉值和注解 enums保持一致
    @EnumValidateAnnotation(enums = {"from1", "from2"}, message = "上報來源錯誤")
    String from;

    @MyAnnotation(age = 26)
    public void test() {
    }

}

缺點
這種使用方式,缺點比較明顯,就是如果要修改的話,不僅枚舉的地方要修改,使用注解校驗的地方因為只寫了String 的值,所以注解的enums參數也需要修改,兩個地方不能放到一起維護,有遺漏的防線。

2. 對枚舉的校驗
枚舉的校驗就有點傷,注解的聲明是不允許泛型的,這意味着我們無法優雅的直接將不同的泛型用同一個校驗器校驗,除非使用黑魔法。甚至,注解的屬性,連抽象類、接口、集合都不允許使用,所以想傳入一個lambda表達式,自定義處理方式都是不行的。沒辦法,用反射吧。
校驗注解

package com.springboot.study.tests.annotation;

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;

/**
 * @Author: guodong
 * @Date: 2021/7/24 13:04
 * @Version: 1.0
 * @Description:
 */
@Documented
@Constraint(validatedBy = {EnumStringValidator2.class})
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface EnumValidateAnnotation2 {

    /**
     * 校驗報錯信息
     * @return
     */
    String message() default "";

    /**
     * 校驗分組
     * @return
     */
    Class<?>[] groups() default {};

    /**
     * 附件 用於擴展
     *
     * @return
     */
    Class<? extends Payload>[] payload() default {};

    /**
     * 允許的枚舉
     * @return
     */
    Class<? extends Enum<?>> enumClass();

    /**
     * 校驗調用的枚舉類的方法
     * @return
     */
    String method();

}

校驗實現

package com.springboot.study.tests.annotation;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Objects;

/**
 * @Author: guodong
 * @Date: 2021/7/24 13:07
 * @Version: 1.0
 * @Description:
 */
public class EnumStringValidator2 implements ConstraintValidator<EnumValidateAnnotation2, String> {

    private String methodStr;

    private Class<? extends Enum> enumClass;

    @Override
    public void initialize(EnumValidateAnnotation2 constraintAnnotation) {
        methodStr = constraintAnnotation.method();
        enumClass = constraintAnnotation.enumClass();
    }

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        if (value == null) {
            return false;
        }
        try {
            // 反射獲取校驗需要調用的枚舉的方法
            Method method = enumClass.getMethod(methodStr);
            boolean result = false;
            // 獲取所有的枚舉值
            Enum[] enums = enumClass.getEnumConstants();
            // 對每一個枚舉值調用 校驗的方法,獲取返回值,和入參作比較
            for (Enum e : enums) {
                Object returnValue = method.invoke(e);
                result = Objects.equals(returnValue, value);
            }
            return result;
        } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
            // 異常處理
        } catch (Throwable throwable) {
            // 異常處理
        }
        return false;
    }

}

使用示例

package com.springboot.study.tests.annotation;

import lombok.Getter;

/**
 * @Author: guodong
 * @Date: 2021/7/24 13:11
 * @Version: 1.0
 * @Description:
 */
public enum FromEnum {

    /**
     * 來源1
     */
    form1("form1"),

    /**
     * 來源2
     */
    form2("form2");

    @Getter
    String from;

    FromEnum(String from) {
        this.from = from;
    }

}
package com.springboot.study.tests.annotation;

import lombok.Data;

import javax.validation.constraints.NotNull;

/**
 * @Author: guodong
 * @Date: 2021/7/24 10:34
 * @Version: 1.0
 * @Description:
 */
@Data
public class Student {

    @NotNull(message = "請求來源不能為空")
    @EnumValidateAnnotation2(enumClass = FromEnum.class, method = "getFrom", message = "上報來源錯誤")
    String from;

    @MyAnnotation(age = 26)
    public void test() {
    }

}

缺點與一丟丟改進
使用反射,缺點明顯,就是性能下降,尤其上面代碼對每一個枚舉反射調用方法,改進的話,就是在枚舉中寫一個特定方法,專門用來做這種入參到枚舉值的轉換,轉換成功,則說明入參正確,否則,說明入參錯誤。
使用示例

package com.springboot.study.tests.annotation;

public interface EnumValidate<T>{

    boolean inEnum(T value);

}
package com.springboot.study.tests.annotation;

import lombok.Getter;

/**
 * @Author: guodong
 * @Date: 2021/7/24 16:04
 * @Version: 1.0
 * @Description:
 */
public enum FromEnum2 implements EnumValidate<String>{

    /**
     * 來源1
     */
    form1("form1"),

    /**
     * 來源2
     */
    form2("form2");

    @Getter
    String from;

    FromEnum2(String from) {
        this.from = from;
    }

    public static FromEnum of(String desc) {
        for (FromEnum from : FromEnum.values()) {
            if (from.getFrom().equalsIgnoreCase(desc)) {
                return from;
            }
        }
        return null;
    }

    @Override
    public boolean inEnum(String value){
        return of(value) != null;
    }

}

校驗器實現 

public class EnumStringValidator implements ConstraintValidator<EnumValidateAnnotation, String> {

    private String methodStr;

    private Class<? extends Enum> enumClass;

    @Override
    public void initialize(EnumValidateAnnotation constraintAnnotation) {
        methodStr = constraintAnnotation.method();
        enumClass = constraintAnnotation.enumClass();
    }

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        if (value == null) {
            return false;
        }
        EnumValidate[] enums = enumClass.getEnumConstants();
        if(enums ==null || enums.length == 0){
          return false;   
        }
        return enums[0].inEnum(value);
    }
}

補充內容
校驗器使用,可以使用這種方式對枚舉注解入參進行校驗,參數是否合法化,或者是使用springboot的注解@valid等注解對入參進行校驗。

private static Validator validator = Validation.byProvider(HibernateValidator.class)
        .configure()
        .failFast(true)
        .buildValidatorFactory()
        .getValidator();

public static <T> void validParam(T param) {
    validParamNonNull(param);
    Set<ConstraintViolation<T>> constraintViolations = validator.validate(param, Default.class);
    StringBuilder sb = new StringBuilder();
    if (constraintViolations != null && !constraintViolations.isEmpty()) {
        for (ConstraintViolation<T> constraintViolation : constraintViolations) {
            sb.append(constraintViolation.getPropertyPath())
                .append(":")
                .append(constraintViolation.getMessage())
                .append(".");
        }
        throw new IllegalArgumentException(sb.toString());
    }
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM