JSR 303 验证,如果一个字段等于“某物”,则这些其他字段不应为空
Posted
技术标签:
【中文标题】JSR 303 验证,如果一个字段等于“某物”,则这些其他字段不应为空【英文标题】:JSR 303 Validation, If one field equals "something", then these other fields should not be null 【发布时间】:2012-03-06 06:25:03 【问题描述】:我希望使用 JSR-303 javax.validation
进行一些自定义验证。
我有一个领域。如果在这个字段中输入了某个值,我想要求其他几个字段不是null
。
我正在努力解决这个问题。不确定我会称之为什么来帮助找到解释。
任何帮助将不胜感激。我对此很陌生。
目前我正在考虑自定义约束。但我不确定如何从注释中测试依赖字段的值。基本上我不确定如何从注释中访问面板对象。
public class StatusValidator implements ConstraintValidator<NotNull, String>
@Override
public void initialize(NotNull constraintAnnotation)
@Override
public boolean isValid(String value, ConstraintValidatorContext context)
if ("Canceled".equals(panel.status.getValue()))
if (value != null)
return true;
else
return false;
这是panel.status.getValue();
给我带来了麻烦.. 不知道如何做到这一点。
【问题讨论】:
【参考方案1】:示例如下:
package io.quee.sample.javax;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import javax.validation.ConstraintViolation;
import javax.validation.Valid;
import javax.validation.Validator;
import javax.validation.constraints.Pattern;
import java.util.Set;
/**
* Created By [**Ibrahim Al-Tamimi **](https://www.linkedin.com/in/iloom/)
* Created At **Wednesday **23**, September 2020**
*/
@SpringBootApplication
public class SampleJavaXValidation implements CommandLineRunner
private final Validator validator;
public SampleJavaXValidation(Validator validator)
this.validator = validator;
public static void main(String[] args)
SpringApplication.run(SampleJavaXValidation.class, args);
@Override
public void run(String... args) throws Exception
Set<ConstraintViolation<SampleDataCls>> validate = validator.validate(new SampleDataCls(SampleTypes.TYPE_A, null, null));
System.out.println(validate);
public enum SampleTypes
TYPE_A,
TYPE_B;
@Valid
public static class SampleDataCls
private final SampleTypes type;
private final String valueA;
private final String valueB;
public SampleDataCls(SampleTypes type, String valueA, String valueB)
this.type = type;
this.valueA = valueA;
this.valueB = valueB;
public SampleTypes getType()
return type;
public String getValueA()
return valueA;
public String getValueB()
return valueB;
@Pattern(regexp = "TRUE")
public String getConditionalValueA()
if (type.equals(SampleTypes.TYPE_A))
return valueA != null ? "TRUE" : "";
return "TRUE";
@Pattern(regexp = "TRUE")
public String getConditionalValueB()
if (type.equals(SampleTypes.TYPE_B))
return valueB != null ? "TRUE" : "";
return "TRUE";
【讨论】:
【参考方案2】:在这种情况下,我建议编写一个自定义验证器,它将在类级别验证(以允许我们访问对象的字段)只有当另一个字段具有特定值时才需要一个字段。请注意,您应该编写获取 2 个字段名称并仅使用这 2 个字段的通用验证器。要要求多个字段,您应该为每个字段添加此验证器。
使用下面的代码作为一个想法(我没有测试过)。
验证器接口
/**
* Validates that field @code dependFieldName is not null if
* field @code fieldName has value @code fieldValue.
**/
@Target(TYPE, ANNOTATION_TYPE)
@Retention(RUNTIME)
@Repeatable(NotNullIfAnotherFieldHasValue.List.class) // only with hibernate-validator >= 6.x
@Constraint(validatedBy = NotNullIfAnotherFieldHasValueValidator.class)
@Documented
public @interface NotNullIfAnotherFieldHasValue
String fieldName();
String fieldValue();
String dependFieldName();
String message() default "NotNullIfAnotherFieldHasValue.message";
Class<?>[] groups() default ;
Class<? extends Payload>[] payload() default ;
@Target(TYPE, ANNOTATION_TYPE)
@Retention(RUNTIME)
@Documented
@interface List
NotNullIfAnotherFieldHasValue[] value();
验证器实现
/**
* Implementation of @link NotNullIfAnotherFieldHasValue validator.
**/
public class NotNullIfAnotherFieldHasValueValidator
implements ConstraintValidator<NotNullIfAnotherFieldHasValue, Object>
private String fieldName;
private String expectedFieldValue;
private String dependFieldName;
@Override
public void initialize(NotNullIfAnotherFieldHasValue annotation)
fieldName = annotation.fieldName();
expectedFieldValue = annotation.fieldValue();
dependFieldName = annotation.dependFieldName();
@Override
public boolean isValid(Object value, ConstraintValidatorContext ctx)
if (value == null)
return true;
try
String fieldValue = BeanUtils.getProperty(value, fieldName);
String dependFieldValue = BeanUtils.getProperty(value, dependFieldName);
if (expectedFieldValue.equals(fieldValue) && dependFieldValue == null)
ctx.disableDefaultConstraintViolation();
ctx.buildConstraintViolationWithTemplate(ctx.getDefaultConstraintMessageTemplate())
.addNode(dependFieldName)
.addConstraintViolation();
return false;
catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException ex)
throw new RuntimeException(ex);
return true;
验证器使用示例(hibernate-validator >= 6 with Java 8+)
@NotNullIfAnotherFieldHasValue(
fieldName = "status",
fieldValue = "Canceled",
dependFieldName = "fieldOne")
@NotNullIfAnotherFieldHasValue(
fieldName = "status",
fieldValue = "Canceled",
dependFieldName = "fieldTwo")
public class SampleBean
private String status;
private String fieldOne;
private String fieldTwo;
// getters and setters omitted
验证器使用示例(hibernate-validator
@NotNullIfAnotherFieldHasValue.List(
@NotNullIfAnotherFieldHasValue(
fieldName = "status",
fieldValue = "Canceled",
dependFieldName = "fieldOne"),
@NotNullIfAnotherFieldHasValue(
fieldName = "status",
fieldValue = "Canceled",
dependFieldName = "fieldTwo")
)
public class SampleBean
private String status;
private String fieldOne;
private String fieldTwo;
// getters and setters omitted
请注意,验证器实现使用 commons-beanutils
库中的 BeanUtils
类,但您也可以使用 BeanWrapperImpl
from Spring Framework。
另请参阅这个很棒的答案:Cross field validation with Hibernate Validator (JSR 303)
【讨论】:
@Benedictus 这个示例只适用于字符串,但您可以修改它以适用于任何对象。有 2 种方法:1) 使用要验证的类参数化验证器(而不是Object
)。在这种情况下,您甚至不需要使用反射来获取值,但在这种情况下验证器变得不那么通用 2)使用来自 Spring Framework(或其他库)的BeanWrapperImp
及其getPropertyValue()
方法。在这种情况下,您将能够获得 Object
的值并转换为您需要的任何类型。
是的,但是你不能将 Object 作为注解参数,所以你需要为你想要验证的每种类型设置一堆不同的注解。
是的,这就是我所说的“在这种情况下验证器变得不那么通用”的意思。
我想将这个技巧用于 protoBuffer 类。这很有帮助(:
不错的解决方案。对构建自定义注释非常有帮助!【参考方案3】:
这是我的看法,尽量保持简单。
界面:
@Target(TYPE, ANNOTATION_TYPE)
@Retention(RUNTIME)
@Constraint(validatedBy = OneOfValidator.class)
@Documented
public @interface OneOf
String message() default "one.of.message";
Class<?>[] groups() default ;
Class<? extends Payload>[] payload() default ;
String[] value();
验证实现:
public class OneOfValidator implements ConstraintValidator<OneOf, Object>
private String[] fields;
@Override
public void initialize(OneOf annotation)
this.fields = annotation.value();
@Override
public boolean isValid(Object value, ConstraintValidatorContext context)
BeanWrapper wrapper = PropertyAccessorFactory.forBeanPropertyAccess(value);
int matches = countNumberOfMatches(wrapper);
if (matches > 1)
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 : fields)
Object value = wrapper.getPropertyValue(field);
boolean isPresent = detectOptionalValue(value);
if (value != null && isPresent)
matches++;
return matches;
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();
用法:
@OneOf("stateType", "modeType")
public class OneOfValidatorTestClass
private StateType stateType;
private ModeType modeType;
消息:
one.of.too.many.matches.message=Only one of the following fields can be specified: value
one.of.no.matches.message=Exactly one of the following fields must be specified: value
【讨论】:
【参考方案4】:定义必须验证为真的方法并将@AssertTrue
注解放在其顶部:
@AssertTrue
private boolean isOk()
return someField != something || otherField != null;
方法必须以'is'开头。
【讨论】:
我使用了你的方法并且它有效,但我不知道如何获取消息。你碰巧知道吗? 这是迄今为止最有效的选择。谢谢! @anaBad:AssertTrue 注释可以接受自定义消息,就像其他约束注释一样。 @ErnestKiwele 感谢您的回答,但我的问题不在于设置消息,而是在我的 jsp 中获取它。我的模型具有以下功能:@AssertTrue(message="La reference doit etre un URL") public boolean isReferenceOk() return origine!=Origine.Evolution||reference.contains("http://jira.bcaexpertise.org");
而这在我的 jsp 中:<th><form:label path="reference"><s:message code="reference"/></form:label></th><td><form:input path="reference" cssErrorClass="errorField"/><br/><form:errors path="isReferenceOk" cssClass="error"/></td>
但它会引发错误。
@ErnestKiwele 没关系,我想通了,我创建了一个布尔属性,在调用 setReference() 时设置。
我必须公开该方法【参考方案5】:
你应该使用自定义DefaultGroupSequenceProvider<T>
:
ConditionalValidation.java
// Marker interface
public interface ConditionalValidation
MyCustomFormSequenceProvider.java
public class MyCustomFormSequenceProvider
implements DefaultGroupSequenceProvider<MyCustomForm>
@Override
public List<Class<?>> getValidationGroups(MyCustomForm myCustomForm)
List<Class<?>> sequence = new ArrayList<>();
// Apply all validation rules from ConditionalValidation group
// only if someField has given value
if ("some value".equals(myCustomForm.getSomeField()))
sequence.add(ConditionalValidation.class);
// Apply all validation rules from default group
sequence.add(MyCustomForm.class);
return sequence;
MyCustomForm.java
@GroupSequenceProvider(MyCustomFormSequenceProvider.class)
public class MyCustomForm
private String someField;
@NotEmpty(groups = ConditionalValidation.class)
private String fieldTwo;
@NotEmpty(groups = ConditionalValidation.class)
private String fieldThree;
@NotEmpty
private String fieldAlwaysValidated;
// getters, setters omitted
另见related question on this topic。
【讨论】:
有趣的做法。不过,答案可能需要更多地解释它是如何工作的,因为在我看到发生了什么之前我必须阅读它两次...... 您好,我实施了您的解决方案,但遇到了问题。没有对象被传递给getValidationGroups(MyCustomForm myCustomForm)
方法。你能在这里帮忙吗? :***.com/questions/44520306/…
@user238607 getValidationGroups(MyCustomForm myCustomForm) 每个 bean 实例都会调用很多次,并且有时会传递 null。如果它通过 null,你就忽略它。【参考方案6】:
另一种方法是创建一个(受保护的)getter,它返回一个包含所有相关字段的对象。示例:
public class MyBean
protected String status;
protected String name;
@StatusAndSomethingValidator
protected StatusAndSomething getStatusAndName()
return new StatusAndSomething(status,name);
StatusAndSomethingValidator 现在可以访问 StatusAndSomething.status 和 StatusAndSomething.something 并进行相关检查。
【讨论】:
以上是关于JSR 303 验证,如果一个字段等于“某物”,则这些其他字段不应为空的主要内容,如果未能解决你的问题,请参考以下文章
使用 JSR 303 在 JSF 中使用内联消息进行跨字段验证
Java Hibernate Validator JSR-303验证
Java Hibernate Validator JSR-303验证