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()); } }