[英]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() }
}
}
我认为这个链接会有所帮助:
我知道我参加聚会有点晚了(确切地说是晚了 11 年),但我仍然想做出贡献。
这里提供的答案很棒,它们在大多数情况下都能解决问题,但在我看来,它们缺乏个性化的感觉。 我是什么意思?
所有解决方案都创建ConstraintValidator<EnumValidator, String>
并在其中实现验证逻辑。
这很好,它解决了问题,但是,如果我想通过枚举的 toString() 进行比较会发生什么,或者更好的是,我有另一个我想通过名称进行比较,两个不同的比较。 为此,有必要为所需的每种比较类型实现一个ConstraintValidator
,而实际上它们的逻辑非常相似。
在我的特殊情况下,一个非常古老的系统进行了比较,一些与toUpperCase
,另一些与toLoweCase
,另一些与trim
,一些与name
,另一些与toString
,完全混乱,其想法是将所有这些概括为相同的行为。
我向您展示的解决方案将@Rajeev 的出色答案与必要的定制相结合,以便能够重用ConstraintValidator
并为每个不同的枚举实现一种比较方法。
首先,@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
以便于记录日志,而springframework
的Assert
用于验证 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 })
注释。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.