我希望使用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();
给我带来麻烦..不知道如何实现这一目标。
答案 0 :(得分:84)
在这种情况下,我建议编写自定义验证器,它将在类级别进行验证(以允许我们访问对象的字段),只有当另一个字段具有特定值时才需要一个字段。请注意,您应该编写通用验证器,它获取2个字段名称并仅使用这2个字段。要要求多个字段,您应该为每个字段添加此验证器。
使用以下代码作为一个想法(我没有测试它)。
验证程序界面
/**
* Validates that field {@code dependFieldName} is not null if
* field {@code fieldName} has value {@code fieldValue}.
**/
@Target({TYPE, ANNOTATION_TYPE})
@Retention(RUNTIME)
@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;
}
}
验证器用法示例
@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
}
请注意,验证程序实现使用BeanUtils
库中的commons-beanutils
类,但您也可以使用BeanWrapperImpl
from Spring Framework。
另见这个好答案:Cross field validation with Hibernate Validator (JSR 303)
答案 1 :(得分:78)
定义必须验证为true的方法并将@AssertTrue
注释放在其顶部:
@AssertTrue
private boolean isOk() {
return someField != something || otherField != null;
}
该方法必须以&#39; is&#39;。
开头答案 2 :(得分:13)
您应该使用自定义DefaultGroupSequenceProvider<T>
:
// Marker interface
public interface ConditionalValidation {}
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;
}
}
@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
}
答案 3 :(得分:4)
这是我对它的看法,试图让它尽可能简单。
界面:
@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 :(得分:3)
另一种方法是创建一个(受保护的)getter,它返回一个包含所有依赖字段的对象。例如:
public class MyBean {
protected String status;
protected String name;
@StatusAndSomethingValidator
protected StatusAndSomething getStatusAndName() {
return new StatusAndSomething(status,name);
}
}
StatusAndSomethingValidator现在可以访问StatusAndSomething.status和StatusAndSomething.something并进行相关检查。
答案 5 :(得分:0)
以下示例:
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";
}
}
}