使用枚举值和注释的 Java 字符串验证

发布于 2024-11-14 20:22:31 字数 550 浏览 8 评论 0原文

我想使用注释根据一组值验证字符串。

我想要的基本上是这样的:

@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
}


我想不出通过自定义注释来实现此目的的方法。
有人请帮助我。

I want to validate a string against a set of values using annotations.

What I want is basically this:

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

int maxValue;
int minValue;
int precision;

or

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

int maxValue;
int minValue;
int precision;

I also want to do some validation on other variables depending upon the value set in dataType:

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

I can't think of a way to achieve this by custom annotations.
Somebody please help me.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(10

比忠 2024-11-21 20:22:32

我知道我来晚了一点(确切地说晚了 11 年),但我仍然愿意做出贡献。

这里提供的答案很好,它们在大多数情况下解决了问题,但在我看来,它们缺乏个性化的触感。
我是什么意思?

所有解决方案都会创建 ConstraintValidator 并在其中实现验证逻辑。

这很好,它解决了问题,但是,如果我想通过枚举的 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();
}

请记住,枚举类将出现在 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 来验证空值。

它的使用就像预期的一样简单:

    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 就是在所有应用程序中重用,并且更改只会影响负责它们的类,而不会破坏系统逻辑的其余部分。

不管你相信与否,这个解决方案保证了我的加薪,我希望我能为你做类似的事情。 :+1:

I know I'm a little late to the party (11 years late to be exact), but I'd still like to contribute.

The answers presented here are great, they solve the problem in most cases, but in my opinion they lack the personalization touch.
What do I mean?

All solutions create the ConstraintValidator<EnumValidator, String> and implement the validation logic in it.

And that is great, it solve the problem, but, what happens if I want to make the comparison by the toString() of the enum, or better, I have another one that I want to compare by the name, two different comparisons. For this, it would be necessary to implement a ConstraintValidator for each type of comparison that is needed, when in fact their logic is VERY similar.

In my particular case, a very old system had comparisons, some with toUpperCase, others with toLoweCase, others with trim, some with name, others with toString, total chaos, and the idea was to generalize all this in the same behavior.

The solution that I present to you combines the excellent answer of @Rajeev with the necessary customization to be able to reuse the ConstraintValidator and implement only one comparison method for each different enum.

The general idea: Make the emun implement an interface to standardize the comparison.

First of all, the @annotation, nothing fancy:


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

Keep in mind that the enum class will be presented in the target field.

Now the interface to generalize the behavior:

public interface EnumValidatorComparator<T> {

    public boolean test(T other);
}

A general combination of these two elements results in an enum with the generalized comparison behavior inside it, which in case it changes would only affect said implementation and no other elements of the system.

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

}

Finally the ConstraintValidator, this is where the 'magic' happens.


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

Note: lombok dependencies are used for @Slf4j for easy logging and springframework's Assert for validations of null value.

its use is as simple as expected:

    public class People {

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

In this way, if you have another enum with another comparison logic, it is as simple as creating said enum with its embedded logic, like:

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

}

And this way the ConstraintValidator it's reused along all application, and changes only affect the class responsible for them without breaking the rest of the system logic.

Believe it or not this solution guaranteed me a raise at work, I hope I do something similar for you. :+1:

我乃一代侩神 2024-11-21 20:22:32

我采纳 Rajeev Singla 的回复 https://stackoverflow.com/a/21070806/8923905,只是为了优化代码和允许 String 参数为 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());
        }

    }
}

I take up Rajeev Singla's response https://stackoverflow.com/a/21070806/8923905, just to optimize the code and allow the String parameter to be null, if in your application it is not mandatory and can be empty :

1- Remove the @NotNull annotation on the Interface

2- See the modified code below for the implementation.

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

    }
}
不一样的天空 2024-11-21 20:22:32

我也想补充我的两分钱。

在建议的解决方案中,枚举类型成员被替换为字符串,以便能够使用 (javax|jakarta).validation 来验证它。

我在同样的思考中挣扎,宁愿不将枚举属性更改为字符串,而是拦截 Jackson 抛出的 InvalidFormatException。

我正在使用 Spring boot 休息控制器,因此异常是由控制器建议捕获的。

该解决方案是用 Kotlin 编写的。

override fun handleHttpMessageNotReadable(
    ex: HttpMessageNotReadableException,
    headers: HttpHeaders,
    status: HttpStatusCode,
    request: WebRequest
): ResponseEntity<Any>? {
    return if (ex.cause is InvalidFormatException && (ex.cause as InvalidFormatException).targetType.isEnum) {
        val cause = ex.cause as InvalidFormatException
        val invalidValue = cause.value
        val enumClass = cause.targetType
        val fieldPath = prettyJsonPath(cause.path)
        val entries = enumClass.enumConstants.joinToString(", ")
        val apiError = ApiError(
            status = HttpStatus.BAD_REQUEST,
            message = "Request is bad, please check provided errors.",
            errors = listOf("Invalid value ($invalidValue) for $fieldPath, expected a value in [$entries].")
        )
        handleExceptionInternal(ex, apiError, headers, apiError.status, request)
    } else {
        super.handleHttpMessageNotReadable(ex, headers, status, request)
    }
}

private fun prettyJsonPath(path: List<JsonMappingException.Reference>): String {
    return path.mapIndexed { index, ref ->
        val dot = if (index == 0 || ref.index >= 0) "" else "."
        val part = if (ref.index == -1) ref.fieldName else "[${ref.index}]"
        return@mapIndexed "$dot$part"
    }.joinToString("")
}

I would like to add my two cents too.

In the proposed solutions the enum type member is replaced with a string to enable using (javax|jakarta).validation for validating it.

I struggled with the same contemplation and had rather not change my enum attributes to strings, instead intercept the InvalidFormatException thrown by Jackson.

I'm using Spring boot rest controllers, and so the exception is caught by a controller advice.

The solution is written in Kotlin.

override fun handleHttpMessageNotReadable(
    ex: HttpMessageNotReadableException,
    headers: HttpHeaders,
    status: HttpStatusCode,
    request: WebRequest
): ResponseEntity<Any>? {
    return if (ex.cause is InvalidFormatException && (ex.cause as InvalidFormatException).targetType.isEnum) {
        val cause = ex.cause as InvalidFormatException
        val invalidValue = cause.value
        val enumClass = cause.targetType
        val fieldPath = prettyJsonPath(cause.path)
        val entries = enumClass.enumConstants.joinToString(", ")
        val apiError = ApiError(
            status = HttpStatus.BAD_REQUEST,
            message = "Request is bad, please check provided errors.",
            errors = listOf("Invalid value ($invalidValue) for $fieldPath, expected a value in [$entries].")
        )
        handleExceptionInternal(ex, apiError, headers, apiError.status, request)
    } else {
        super.handleHttpMessageNotReadable(ex, headers, status, request)
    }
}

private fun prettyJsonPath(path: List<JsonMappingException.Reference>): String {
    return path.mapIndexed { index, ref ->
        val dot = if (index == 0 || ref.index >= 0) "" else "."
        val part = if (ref.index == -1) ref.fieldName else "[${ref.index}]"
        return@mapIndexed "$dot$part"
    }.joinToString("")
}
花辞树 2024-11-21 20:22:32

您可以将 @NotNull 注释与您的注释结合使用。
要使用它,您需要在 ValidateString 中添加 @Target( { ANNOTATION_TYPE }) 注释。

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

You can use @NotNull annotation in conjunction with yours.
To use that you need to add @Target( { ANNOTATION_TYPE }) annotation in ValidateString.

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

深海少女心 2024-11-21 20:22:31

这是使用 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 {};

}

上述类的实现

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;

So here is the code being using Spring validation and works great for me.
Full code is given below.


@EnumValidator annotation definition:

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 {};

}

Implementation of the above 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());
        }
    }

}

Usage of the above annotation is very simple

 @JsonProperty("lead_id")
 @EnumValidator(
     enumClazz = DefaultEnum.class,
     message = "This error is coming from the enum class",
     groups = {Group1.class}
 )
 private String leadId;
别理我 2024-11-21 20:22:31

这就是我所做的。

注释

public @interface ValidateString {

    String[] acceptedValues();

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

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

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

验证类

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;

现在我需要弄清楚如何实现条件检查
IE。如果是字符串,那么 maxValue 和 minValue 应该为 null 或零..

有什么想法吗?

This is what I did.

Annotation

public @interface ValidateString {

    String[] acceptedValues();

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

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

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

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

}

And i used it like

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

Long maxValue;
Long minValue;

Now I need to figure out how to implement conditional check
ie. if String then maxValue and minValue should be null or Zero..

Any ideas?

南城旧梦 2024-11-21 20:22:31

放弃字符串表示形式,并进行真正的枚举。

public enum DataType {
   STRING,
   BOOLEAN,
   INTEGER;
}

这样,您就不必对先前的 String dataType 变量进行字符串比较来确定它是否在枚举中。顺便说一句,它也使得不可能为成员变量 dataType 分配无效值,并且由于枚举在类加载器中保证是单例,因此它还节省了内存占用。

更改代码以使用枚举是值得的。但是,假设您不能,您至少可以更改注释以使用枚举。

@ValidateString(DataType.STRING) String dataType;

这样你的 ValidateString 注释至少可以从枚举中受益,即使代码的其余部分没有。

现在,在极少数情况下您根本无法使用枚举,您可以设置静态公共整数,它映射每个接受的值。

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 类中实现的枚举。

Ditch the String representation, and do a real enum.

public enum DataType {
   STRING,
   BOOLEAN,
   INTEGER;
}

That way you avoid ever having to do string comparison of the previous String dataType variable to determine if it is in the enum. As an aside, it also makes it impossible to assign a non-valid value to the member variable dataType and since enums are guaranteed to be singletons within the class loader, it also saves on memory footprint.

It's worth the effort to change your code to use enums. However, assuming that you can't, you can at least change the annotation to use enums.

@ValidateString(DataType.STRING) String dataType;

and that way your ValidateString annotation at least gets to benefit from enums, even if the rest of the code doesn't.

Now on the extremely rare chance that you can't use an enumeration at all, you can set static public integers, which map each accepted value.

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

However, if you use a String for the annotation parameter, we don't have a type checking system which extends into the type to specify that only particular values are allowed. In other words, Java lacks the ability to do something like this:

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

which would throw an error if you attempted to assign

 oddInt = 4;

Why is this important? Because if it doesn't apply to regular Java, then it cannot apply to the enumeration which is implemented in regular Java classes.

无人问我粥可暖 2024-11-21 20:22:31

我对 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() }
    }
}

My attempt for a kotlin one:

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() }
    }
}
香草可樂 2024-11-21 20:22:31

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

Little bit of improvisation with 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());
  }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文