在Hibernate Validator 4.x中是否有跨字段验证的实现(或第三方实现)?如果不是,实现跨字段验证器的最干净的方法是什么?
例如,如何使用API来验证两个bean属性是否相等(例如验证密码字段与密码验证字段是否匹配)。
在注释中,我期望如下内容:
public class MyBean {
@Size(min=6, max=50)
private String pass;
@Equals(property="pass")
private String passVerify;
}
与问题相关的解决方案:
如何访问在注释属性中描述的字段
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Match {
String field();
String message() default "";
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = MatchValidator.class)
@Documented
public @interface EnableMatchConstraint {
String message() default "Fields must match!";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
public class MatchValidator implements ConstraintValidator<EnableMatchConstraint, Object> {
@Override
public void initialize(final EnableMatchConstraint constraint) {}
@Override
public boolean isValid(final Object o, final ConstraintValidatorContext context) {
boolean result = true;
try {
String mainField, secondField, message;
Object firstObj, secondObj;
final Class<?> clazz = o.getClass();
final Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
if (field.isAnnotationPresent(Match.class)) {
mainField = field.getName();
secondField = field.getAnnotation(Match.class).field();
message = field.getAnnotation(Match.class).message();
if (message == null || "".equals(message))
message = "Fields " + mainField + " and " + secondField + " must match!";
firstObj = BeanUtils.getProperty(o, mainField);
secondObj = BeanUtils.getProperty(o, secondField);
result = firstObj == null && secondObj == null || firstObj != null && firstObj.equals(secondObj);
if (!result) {
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate(message).addPropertyNode(mainField).addConstraintViolation();
break;
}
}
}
} catch (final Exception e) {
// ignore
//e.printStackTrace();
}
return result;
}
}
如何使用它?是这样的:
@Entity
@EnableMatchConstraint
public class User {
@NotBlank
private String password;
@Match(field = "password")
private String passwordConfirmation;
}
我建议你另一种可能的解决办法。也许不那么优雅,但更容易!
public class MyBean {
@Size(min=6, max=50)
private String pass;
private String passVerify;
@NotNull
private LocalDate passExpiry;
@NotNull
private LocalDate dateOfJoining;
@AssertTrue(message = "Fields `pass` and `passVerify` should be equal")
// Any method name is ok als long it begins with `is`
private boolean isValidPass() {
//return pass == null && passVerify == null || pass.equals(passVerify);
// Since Java 7:
return Objects.equals(pass, passVerify);
}
@AssertTrue(message = "Field `passExpiry` should be later than `dateOfJoining`")
// Other rules can also be validated in other methods
private boolean isPassExpiryAfterDateOfJoining() {
return dateOfJoining.isBefore(passExpiry);
}
}
isValid()和isPassExpiryAfterDateOfJoining()方法由验证器自动调用。在ConstraintViolations中报告的属性路径将从方法名中提取:valid和passExpiryAfterDateOfJoining。
我喜欢Jakub Jirutka使用Spring表达式语言的想法。如果您不想添加另一个库/依赖项(假设您已经使用了Spring),下面是他的想法的简化实现。
约束:
@Constraint(validatedBy=ExpressionAssertValidator.class)
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ExpressionAssert {
String message() default "expression must evaluate to true";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
String value();
}
验证器:
public class ExpressionAssertValidator implements ConstraintValidator<ExpressionAssert, Object> {
private Expression exp;
public void initialize(ExpressionAssert annotation) {
ExpressionParser parser = new SpelExpressionParser();
exp = parser.parseExpression(annotation.value());
}
public boolean isValid(Object value, ConstraintValidatorContext context) {
return exp.getValue(value, Boolean.class);
}
}
像这样应用:
@ExpressionAssert(value="pass == passVerify", message="passwords must be same")
public class MyBean {
@Size(min=6, max=50)
private String pass;
private String passVerify;
}