java - 春季启动验证 - 至少一个交叉字段
问题描述
在 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
}
现在我正在寻找一个动态的解决方案,因为我需要轻松地扩展这个类。我该怎么做?
解决方案
使用 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
}
推荐阅读
- java - Servlet 上下文不仅在构建 WAR 时设置
- azure-aks - Azure 安全中心发出高度警报“受信任的注册表只允许用于容器映像”
- firebase - Flutter - 如何在firebase集合中的所有文档中添加一个字段
- java - 不同类型列表需要 Dagger @Named 注入?
- c++ - 如何通过另一个不同类型的多维容器创建一个多维容器?
- node.js - 在将 appium 作为 DeprecationWarning 运行之前,我有一个警告。它是什么?
- angular - 我正在尝试实现 refreshCountryList ,它应该返回包含所有国家的 countryList(array of objects) 变量
- reactjs - 处理反应查询突变和反应上下文时的最佳策略
- mediasoup - mediasoup-client TypeError:无法在 createRecvTransport 处读取 null 的属性“_createTransport”
- oop - 设计过程中的SOLID原则思考