Java 请求日期验证:对于具有两个可变参数的任何类

mat*_*th5 4 java validation spring-boot

我正在为请求日期约束创建共享组件,开始日期在结束日期之前。我想接受当前的验证请求,并使其通用,因此我输入(任何类的 Begin 和 EndDate 类成员),它将起作用。如何才能做到这一点?我在请求类上方、下面的 ProductRequest 中使用注释。

注意:如何在注释中设置开始和结束日期参数;它们可能并不总是“开始/结束”字段成员,有时它们可​​能是另一个类中的“开始/结束”。

@DatesRequestConstraint
public class ProductRequest {
    private Long productId;
    private DateTime startDate;
    private DateTime EndDate;
    private List<String> productStatus;
}

@Target({ TYPE, ANNOTATION_TYPE })
@Retention(RUNTIME)
@Constraint(validatedBy = ProductValidator.class)
@Documented
public @interface DatesRequestConstraint {
    String message() default "Invalid dates request.";
    Class <?> [] groups() default {};
    Class <? extends Payload> [] payload() default {};
}

public class ProductValidator implements ConstraintValidator<DatesRequestConstraint, ProductRequest> {

    @Override
    public void initialize(DatesRequestConstraint constraintAnnotation) {
        ConstraintValidator.super.initialize(constraintAnnotation);
    }

    @Override
    public boolean isValid(ProductRequest productRequest, ConstraintValidatorContext constraintValidatorContext) {

    if (productRequest.getStartDate() != null && 
        productRequest.getEndDate() != null && 
        productRequest.getStartDate().isAfter(productRequest.getEndDate())) {
        return false;
    }
    else return true;
}
 
Run Code Online (Sandbox Code Playgroud)

Moe*_*men 5

您可以使用自定义注释来注释 startDate 和 endDate ,例如:

@StartDateField
private DateTime startDate;
@EndDateField
private DateTime endDate;
Run Code Online (Sandbox Code Playgroud)

然后在您的 中isValid(),您可以通过迭代所有类字段(在您的情况下为所有 ProductRequest 字段)并检查以下内容,通过注释访问 startDate 和 endDate 字段:

field.isAnnotationPresent(StartDateField.class)
field.isAnnotationPresent(EndDateField.class)
Run Code Online (Sandbox Code Playgroud)

完整的代码可能如下:

import javax.validation.Constraint;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import javax.validation.Payload;
import java.lang.annotation.*;
import java.util.Arrays;
import java.util.List;

import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

@Target({ ANNOTATION_TYPE.TYPE })
@Retention(RUNTIME)
@Constraint(validatedBy = ProductValidator.class)
@Documented
@interface DatesRequestConstraint {
    String message() default "Invalid dates request.";
    Class <?> [] groups() default {};
    Class <? extends Payload> [] payload() default {};
}

@DatesRequestConstraint
class ProductRequest {
    private Long productId;
    @StartDateField
    private DateTime startDate;
    @EndDateField
    private DateTime EndDate;
    private List<String> productStatus;
}

@Target({ ElementType.FIELD })
@Retention(RUNTIME)
@Documented
@interface StartDateField {
}

@Target({ ElementType.FIELD })
@Retention(RUNTIME)
@Documented
@interface EndDateField {
}

public class ProductValidator implements ConstraintValidator<DatesRequestConstraint, Object> {

    @Override
    public void initialize(DatesRequestConstraint constraintAnnotation) {
        ConstraintValidator.super.initialize(constraintAnnotation);
    }

    @Override
    public boolean isValid(Object requestObject, ConstraintValidatorContext constraintValidatorContext) {

        DateTime startDate = getDateFieldByAnnotation(requestObject, StartDateField.class);
        DateTime endDate = getDateFieldByAnnotation(requestObject, EndDateField.class);
        if (startDate != null &&
                endDate != null &&
                startDate.isAfter(endDate)) {
            return false;
        } else return true;
    }

    private DateTime getDateFieldByAnnotation(Object requestObject, Class<? extends Annotation> annotationClass) {
        return Arrays.stream(requestObject.getClass().getDeclaredFields()).filter(field -> field.isAnnotationPresent(annotationClass)).map(field -> {
            try {
                return field.get(requestObject);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
            return null;
        }).map(DateTime.class::cast).findAny().orElse(null);
    }
}
Run Code Online (Sandbox Code Playgroud)


vto*_*osh 5

你可以:

  1. 实现ConstraintValidator<DatesMatch, Object>以便您可以将@DatesMatch注释应用于任何类型;
  2. 将自定义String字段添加到@DatesMatch注释中,您可以在其中指定要验证的字段的名称;
  3. 在运行时使用反射按指定名称访问字段值。

这里有一个针对多个自定义字段的类级验证的类似示例:Baeldung:Spring MVC 自定义验证(向下滚动到“9.自定义类级验证”)。

根据您的示例进行定制,类似这样的内容应该有效:

@Constraint(validatedBy = DatesMatchValidator.class)
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface DatesMatch {
    String message() default "The dates don't match.";
    String startField();
    String endField();

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

    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @interface List {
        DatesMatch[] value();
    }
}

// Accept a list of items so that you can validate more than one pair of dates on the same object if needed
@DatesMatch.List({
        @DatesMatch(
                startField = "startDate",
                endField = "endDate",
                message = "The end date must be after the start date."
        )
})
public class ProductRequest {
    private Long productId;
    private Instant startDate;
    private Instant endDate;
    private List<String> productStatus;
    /* Getters and setters omitted */
}

public class DatesMatchValidator implements ConstraintValidator<DatesMatch, Object> {
    private String startField;
    private String endField;

    public void initialize(DatesMatch constraintAnnotation) {
        this.startField = constraintAnnotation.startField();
        this.endField = constraintAnnotation.endField();
    }

    public boolean isValid(Object value, ConstraintValidatorContext context) {
        Instant startFieldValue = (Instant) new BeanWrapperImpl(value)
                .getPropertyValue(startField);
        Instant endFieldValue = (Instant) new BeanWrapperImpl(value)
                .getPropertyValue(endField);

        if (startFieldValue == null || endFieldValue == null) {
            return true;
        }

        return endFieldValue.isAfter(startFieldValue);
    }
}
Run Code Online (Sandbox Code Playgroud)

更新:(回应评论):

这个答案很好,允许多对日期,但是类型字符串不安全,人们可以在产品字段中输入任何字段

实施ConstraintValidator<DatesMatch, Object>是一种简单的包罗万象的解决方案,您可以将其应用于任何类别。

ConstraintValidator但是您绝对可以通过为要验证的每种类型(即ConstraintValidator<DatesMatch, ProductRequest>,, ...)实现一个单独的类型,以更类型安全的方式来完成此操作ConstraintValidator<DatesMatch, AnotherRequest>,然后在属性中指定所有类型@Constraint(validatedBy={...})

@Constraint(validatedBy = {ProductRequestDatesMatchValidator.class, AnotherRequestDatesMatchValidator.class})
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface DatesMatch {

    String message() default "Invalid dates request.";

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

@DatesMatch(message = "Start and end dates do not match!")
public class ProductRequest {
    private Long productId;
    private Instant startDate;
    private Instant endDate;
    private List<String> productStatus;
    /* Getters and setters omitted */
}

@DatesMatch(message = "Begin and finish dates do not match!")
public class AnotherRequest {
    private Long productId;
    private Instant beginDate;
    private Instant finishDate;
    private List<String> productStatus;
    /* Getters and setters omitted */
}

public class ProductRequestDatesMatchValidator implements ConstraintValidator<DatesMatch, ProductRequest> {
    @Override
    public boolean isValid(ProductRequest value, ConstraintValidatorContext context) {
        // No need to cast here
        Instant startDate = value.getStartDate();
        Instant endDate = value.getEndDate();

        // You could reuse this logic between each implementation by putting it in a parent class or a utility method
        if (startDate == null || endDate == null) {
            return true;
        }

        return startDate.isBefore(endDate);
    }
}

public class AnotherRequestDatesMatchValidator implements ConstraintValidator<DatesMatch, AnotherRequest> {
    @Override
    public boolean isValid(AnotherRequest value, ConstraintValidatorContext context) {
        Instant beginDate = value.getBeginDate();
        Instant finishDate = value.getFinishDate();

        if (beginDate == null || finishDate == null) {
            return true;
        }

        return beginDate.isBefore(finishDate);
    }
}
Run Code Online (Sandbox Code Playgroud)

但请注意,这仍然不是编译时类型安全的,因为您可以将注释放在@DatesMatch尚未编写实现的类上,并且验证只会在运行时失败。

(您可以使用注释处理来实现编译时类型安全,但这又是另一个话题了。)