首页 > 解决方案 > 春季启动验证 - 至少一个交叉字段

问题描述

在 Spring Boot 中使用跨字段验证时遇到了一些问题。例如,有一个有四个字段的类。第一个字段是必填的,其他都是可选的,但至少有一个可选字段必须存在。

public class DataContainer {

    @NotNull
    private String provider;

    @Valid
    private List<Client> clients;

    @Valid
    private List<Item> items;

    @Valid
    private List<Order> orders;

    // Getter and setter omitted for simplicity
}

现在我正在寻找一个动态的解决方案,因为我需要轻松地扩展这个类。我该怎么做?

标签: javaspringspring-boot

解决方案


使用 Ishikawa Yoshi 的提示,我自己找到了解决方案。这是我对所有感兴趣的人的实现。

首先,我创建了一个新注释

@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = {AtLeastOneOfValidator.class})
public @interface AtLeastOneOf {

    String message() default "{one.of.message}";

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

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

    String[] fields();

    int max() default 2147483647;
}

然后是相关的验证器

public class AtLeastOneOfValidator implements ConstraintValidator<AtLeastOneOf, Object> {

    private String[] fields;
    private int max;

    @Override
    public void initialize(AtLeastOneOf annotation) {
        this.fields = annotation.fields();
        this.max = annotation.max();
    }

    @Override
    public boolean isValid(Object value, ConstraintValidatorContext context) {
        BeanWrapper wrapper = PropertyAccessorFactory.forBeanPropertyAccess(value);

        int matches = countNumberOfMatches(wrapper);

        if (matches > this.max) {
            setValidationErrorMessage(context, "one.of.too.many.matches.message");
            return false;
        } else if (matches == 0) {
            setValidationErrorMessage(context, "one.of.no.matches.message");
            return false;
        }

        return true;
    }

    private int countNumberOfMatches(BeanWrapper wrapper) {
        int matches = 0;
        for (String field : this.fields) {
            Object value = wrapper.getPropertyValue(field);
            boolean isPresent = detectOptionalValue(value);

            if (value != null && isPresent) {
                matches++;
            }
        }
        return matches;
    }

    @SuppressWarnings("rawtypes")
    private boolean detectOptionalValue(Object value) {
        if (value instanceof Optional) {
            return ((Optional) value).isPresent();
        }
        return true;
    }

    private void setValidationErrorMessage(ConstraintValidatorContext context, String template) {
        context.disableDefaultConstraintViolation();
        context.buildConstraintViolationWithTemplate("{" + template + "}").addConstraintViolation();
    }
}

现在可以使用注解了

@AtLeastOneOf(fields = {"clients", "items", "orders"})
public class DataContainer {

    @NotNull
    private String provider;

    @Valid
    private List<Client> clients;

    @Valid
    private List<Item> items;

    @Valid
    private List<Order> orders;

    // Getter and setter omitted for simplicity
}

推荐阅读