簡體   English   中英

Java 使用枚舉值和注釋進行字符串驗證

[英]Java String validation using enum values and annotation

我想使用注釋針對一組值驗證字符串。

我想要的基本上是這樣的:

@ValidateString(enumClass=com.co.enum)
String dataType;

int maxValue;
int minValue;
int precision;

或者

@ValidateString(values={"String","Boolean", "Integer"})
String dataType;

int maxValue;
int minValue;
int precision;


我還想根據dataType中設置的值對其他變量進行一些驗證:

if (dataType = "String") {
    // maxValue, minValue, precision all should be null or zero
}


我想不出通過自定義注釋來實現這一點的方法。
有人請幫助我。

所以這里是使用 Spring 驗證的代碼,對我來說非常有用。 完整代碼如下。


@EnumValidator注解定義:

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import javax.validation.Constraint;
import javax.validation.Payload;
import javax.validation.ReportAsSingleViolation;
import javax.validation.constraints.NotNull;

@Documented
@Constraint(validatedBy = EnumValidatorImpl.class)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@NotNull(message = "Value cannot be null")
@ReportAsSingleViolation
public @interface EnumValidator {

  Class<? extends Enum<?>> enumClazz();

  String message() default "Value is not valid";

  Class<?>[] groups() default {};

  Class<? extends Payload>[] payload() default {};

}


上述class的實現

import java.util.ArrayList;
import java.util.List;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

public class EnumValidatorImpl implements ConstraintValidator<EnumValidator, String> {

    List<String> valueList = null;

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        return valueList.contains(value.toUpperCase());
    }

    @Override
    public void initialize(EnumValidator constraintAnnotation) {
        valueList = new ArrayList<String>();
        Class<? extends Enum<?>> enumClass = constraintAnnotation.enumClazz();

        @SuppressWarnings("rawtypes")
        Enum[] enumValArr = enumClass.getEnumConstants();

        for (@SuppressWarnings("rawtypes") Enum enumVal : enumValArr) {
            valueList.add(enumVal.toString().toUpperCase());
        }
    }

}


上面注解的用法很簡單

 @JsonProperty("lead_id")
 @EnumValidator(
     enumClazz = DefaultEnum.class,
     message = "This error is coming from the enum class",
     groups = {Group1.class}
 )
 private String leadId;

這就是我所做的。

注解

public @interface ValidateString {

    String[] acceptedValues();

    String message() default "{uk.dds.ideskos.validator.ValidateString.message}";

    Class<?>[] groups() default { };

    Class<? extends Payload>[] payload() default { }; 
}

驗證 Class

public class StringValidator implements ConstraintValidator<ValidateString, String>{

    private List<String> valueList;

    @Override
    public void initialize(ValidateString constraintAnnotation) {
        valueList = new ArrayList<String>();
        for(String val : constraintAnnotation.acceptedValues()) {
            valueList.add(val.toUpperCase());
        }
    }

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        return valueList.contains(value.toUpperCase());
    }

}

我用它就像

@ValidateString(acceptedValues={"Integer", "String"}, message="Invalid dataType")
String dataType;

Long maxValue;
Long minValue;

現在我需要弄清楚如何實現條件檢查。 如果是字符串,那么 maxValue 和 minValue 應該是 null 或零..

有任何想法嗎?

拋棄字符串表示,並做一個真正的枚舉。

public enum DataType {
   STRING,
   BOOLEAN,
   INTEGER;
}

這樣,您就不必對先前的String dataType變量進行字符串比較以確定它是否在枚舉中。 順便說一句,它也使得無法為成員變量dataType分配無效值,並且由於枚舉在 class 加載程序中保證是單例,因此它還節省了 memory 占用空間。

將代碼更改為使用枚舉是值得的。 但是,假設您不能,您至少可以將注釋更改為使用枚舉。

@ValidateString(DataType.STRING) String dataType;

這樣您的ValidateString注釋至少可以從枚舉中受益,即使代碼的 rest 沒有。

現在,在您根本無法使用枚舉的極少數情況下,您可以設置 static 公共整數,其中 map 每個接受值。

public class DataType {
  public static final int STRING = 1;
  public static final int BOOLEAN = 2;
  ...
}

但是,如果您使用字符串作為注解參數,我們沒有一個類型檢查系統可以擴展到類型以指定只允許特定值。 換句話說,Java 缺乏這樣的能力:

public int<values=[1,3,5,7..9]> oddInt; 

如果您嘗試分配,這將引發錯誤

 oddInt = 4;

為什么這很重要? 因為如果它不適用於常規 Java,那么它就不能適用於常規 Java 類中實現的枚舉。

Java 8 Stream API 即興創作

import static java.util.stream.Collectors.toList;
import static java.util.stream.Stream.of;
import java.util.List;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

public class EnumValidatorImpl implements ConstraintValidator<EnumValidator, String> 
{
  private List<String> valueList = null;
  @Override
  public boolean isValid(String value, ConstraintValidatorContext context) {
    return valueList.contains(value.toUpperCase());
  }
  @Override
  public void initialize(EnumValidator constraintAnnotation) {
    valueList = of(constraintAnnotation.enumClazz().getEnumConstants()).map(e->e.toString()).collect(toList());
  }
}

我采用 Rajeev Singla 的響應https://stackoverflow.com/a/21070806/8923905 ,只是為了優化代碼並允許字符串參數為 null,如果在您的應用程序中它不是強制性的並且可以為空:

1-刪除接口上的@NotNull注釋

2-請參閱下面的修改代碼以了解實現。

public class EnumValidatorImpl implements ConstraintValidator <EnumValidator,String> {

    private List<String> valueList = null;

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        return null == value || valueList.contains(value.toUpperCase());
    }

    @Override
    public void initialize(EnumValidator constraintAnnotation) {
        valueList = new ArrayList<>();
        Class<? extends Enum<?>> enumClass = constraintAnnotation.enumClass();

        Enum[] enumValArr = enumClass.getEnumConstants();

        for(Enum enumVal : enumValArr) {
            valueList.add(enumVal.toString().toUpperCase());
        }

    }
}

我對 kotlin 的嘗試:

import javax.validation.Constraint
import javax.validation.ConstraintValidator
import javax.validation.ConstraintValidatorContext
import javax.validation.ReportAsSingleViolation
import javax.validation.constraints.NotNull
import kotlin.reflect.KClass

@Constraint(validatedBy = [EnumValidatorImpl::class])
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FIELD)
@NotNull(message = "Value cannot be null")
@ReportAsSingleViolation
annotation class EnumValidator(val enumClazz: KClass<*>, val message: String = "Value is not valid")

class EnumValidatorImpl(private var valueList: List<String>? = null) : ConstraintValidator<EnumValidator, String> {
    override fun isValid(value: String?, context: ConstraintValidatorContext?): Boolean =
            valueList?.contains(value?.toUpperCase()) ?: false

    override fun initialize(constraintAnnotation: EnumValidator) {
        valueList = constraintAnnotation.enumClazz.java.enumConstants.map { it.toString().toUpperCase() }
    }
}

我認為這個鏈接會有所幫助:

https://www.baeldung.com/javax-validations-enums

我知道我參加聚會有點晚了(確切地說是晚了 11 年),但我仍然想做出貢獻。

這里提供的答案很棒,它們在大多數情況下都能解決問題,但在我看來,它們缺乏個性化的感覺。 我是什么意思?

所有解決方案都創建ConstraintValidator<EnumValidator, String>並在其中實現驗證邏輯。

這很好,它解決了問題,但是,如果我想通過枚舉的 toString() 進行比較會發生什么,或者更好的是,我有另一個我想通過名稱進行比較,兩個不同的比較。 為此,有必要為所需的每種比較類型實現一個ConstraintValidator ,而實際上它們的邏輯非常相似。

在我的特殊情況下,一個非常古老的系統進行了比較,一些與toUpperCase ,另一些與toLoweCase ,另一些與trim ,一些與name ,另一些與toString ,完全混亂,其想法是將所有這些概括為相同的行為。

我向您展示的解決方案將@Rajeev 的出色答案與必要的定制相結合,以便能夠重用ConstraintValidator並為每個不同的枚舉實現一種比較方法。

總體思路:讓emun實現一個接口來標准化比較。

首先,@annotation,沒什么特別的:


import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;

@Target(value = ElementType.FIELD)
@Retention(value = RetentionPolicy.RUNTIME)
@Constraint(validatedBy = {EnumValidatorRegister_String.class})
public @interface EnumValidator {

    String message() default "Value is not present in enum list.";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};

    public String detailMessage() default "";

    public Class<? extends Enum<?>> target();
}

請記住,枚舉 class 將顯示在target字段中。

現在泛化行為的接口:

public interface EnumValidatorComparator<T> {

    public boolean test(T other);
}

這兩個元素的一般組合會產生一個枚舉,其中包含一般比較行為,如果它發生變化,只會影響所述實現,不會影響系統的其他元素。

public enum Type implements EnumValidatorComparator<String> {
    a("a"),
    b("b"),
    c("c"),
    d("d"),
    e("e"),
    f("f"),
    g("g"),
    h("h"),
    i("i");

    private final String name;

    private Type(String name) {
        this.name = name;
    }

    @Override
    public boolean test(String other) {
        return this.toString().equalsIgnoreCase(other.trim().toLowerCase());
    }

}

最后是ConstraintValidator ,這就是“魔術”發生的地方。


import java.util.function.BiPredicate;
import java.util.stream.Stream;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.Assert;

@Slf4j
public class EnumValidatorRegister_String implements ConstraintValidator<EnumValidator, String> {

    //general comparator in case EnumValidator don't implement EnumValidatorComparator interface
    private static BiPredicate<? super Enum, String> defaultComparison = (currentEnumValue, testValue) -> {
        return currentEnumValue.toString().equals(testValue);
    };

    //setter for default comparator
    public static void setDefaultEnumComparator(BiPredicate<? super Enum, String> defaultComparison) {
        Assert.notNull(defaultComparison, "Default comparison can't be null");
        EnumValidatorRegister_String.defaultComparison = defaultComparison;
    }

    //Enum class
    private Class<? extends Enum<?>> clazz;
    //values of enum
    private Enum[] valuesArr;

    @Override
    public void initialize(EnumValidator _enum) {
        ConstraintValidator.super.initialize(_enum);
        clazz = _enum.target();
        valuesArr = clazz.getEnumConstants();
    }

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        boolean present;

        //if enum's targes is assignable from EnumValidatorComparator, compare by the `.test`
        if (EnumValidatorComparator.class.isAssignableFrom(clazz)) {
            present = Stream.of(valuesArr).anyMatch((t) -> {
                return ((EnumValidatorComparator<String>) t).test(value);
            });
        } else { //if enum's targes is NOT assignable from EnumValidatorComparator, compare by the default
            present = Stream.of(valuesArr).anyMatch((t) -> {
                return defaultComparison.test(t, value);
            });
        }

        //if the value is NOT present, show custom error
        if (!present) {
            context.disableDefaultConstraintViolation();
            context.buildConstraintViolationWithTemplate(
                    String.format(
                            "'%s' is not one of the one of allowable values: %s".formatted(
                                    value,
                                    Stream.of(valuesArr).map((Object object) -> {
                                        return object.toString();
                                    }).toList().toString()
                            )
                    )
            ).addConstraintViolation();
        }

        return present;
    }
}

注意: lombok依賴項用於@Slf4j以便於記錄日志,而springframeworkAssert用於驗證 null 值。

它的使用和預期的一樣簡單:

    public class People {

        @EnumValidator(target = Type.class)
        private String name;
        
        private String someOtherField;
        
        //getters, setters and constructor
    }

這樣,如果您有另一個帶有另一個比較邏輯的枚舉,它就像使用其嵌入式邏輯創建所述枚舉一樣簡單,例如:

public enum OtherType implements EnumValidatorComparator<String> {
    A("A"),
    B("B"),
    C("C"),
    D("D"),
    E("E");

    private final String name;

    private OtherType(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return name;
    }

    @Override
    public boolean test(String other) {
        return this.toString().equals(other);
    }

}

這樣, ConstraintValidator將在所有應用程序中重復使用,並且更改只會影響負責它們的 class 而不會破壞系統邏輯的 rest。

信不信由你,這個解決方案保證了我在工作中的加薪,我希望我能為你做一些類似的事情。 :+1:

您可以將@NotNull注釋與您的注釋結合使用。 要使用它,您需要在ValidateString中添加@Target( { ANNOTATION_TYPE })注釋。

http://docs.jboss.org/hibernate/validator/4.0.1/reference/en/html/validator-customconstraints.ZFC35FDC70D5FC69D269883A822C7A53

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM