在DTO级别的基于注释的验证发生在字段级别验证例如@NotNull @Size等之前

问题描述

我有一个DTO类,在该类中,一些字段是必填项。 并且,基于另一个可以假定值A和B的类型字段 如果是A型,则只需检查1个项目即可通过;如果是B型,也可以大于1个。

我需要检查列表在DTO类级别中是否至少具有1个值。而且,在自定义批注验证中,我需要根据DTO的类型字段检查列表的大小。

所以,在DTO中

@NotNull
@Size(min = 1)
private List<@NotBlank String> items;

而且,在内部注释

        if (cancelType == CancellationTypeEnum.A && cancelDto.getItems().size() > 1) {

            isValid = false;
            context.disableDefaultConstraintViolation();
            context.buildConstraintViolationWithTemplate(
                    "Only one item can be sent for 'A' cancel type.").addConstraintViolation();

        } 

但是,由于字段级别是稍后发生的,如果我为items字段传递null,则它将转到此批注验证器并由于field为null且我要获取大小而抛出NPE

临时解决方案是我先执行null检查,然后检查大小,但是无论如何在字段级别我们都提供@NotNull。

是否有任何方法可以执行类级别的自定义批注,以在发生字段级别验证之后进行验证。 在这种情况下,由于字段为null且不会转到自定义类级别注释,它将引发字段级别验证

解决方法

您可以结合使用来自文档的JS-303验证组(请参见here)和@Validated

JSR-303 Valid的变体,支持验证规范 组。

例如:

@CheckEnumSize(groups = {Secondary.class})
public class CancelDto {

    public CancelDto(List<String> items) {
        this.items = items;
    }

    @NotNull(groups = {Primary.class})
    @Size(min = 1)
    public List<@NotNull String> items;

    public CancellationTypeEnum cancelType;

    public List<String> getItems() {
        return items;
    }
}
 

其中Primary是一个简单的界面:

public interface Primary {
}

Secondary相同:

public interface Secondary {
}

最后,您可以使用经过验证的方式:

@Service
@Validated({Primary.class,Secondary.class})
public class CancelService {

    public void applyCancel(@Valid CancelDto cancel) {
        //TODO
    }

}

在将以上代码与项目为空的CancelDto一起使用时,您应该获得:

Caused by: javax.validation.ConstraintViolationException: applyCancel.cancel.items: must not be null