在Hibernate Validator 4.x中是否有跨字段验证的实现(或第三方实现)?如果不是,实现跨字段验证器的最干净的方法是什么?
例如,如何使用API来验证两个bean属性是否相等(例如验证密码字段与密码验证字段是否匹配)。
在注释中,我期望如下内容:
public class MyBean {
@Size(min=6, max=50)
private String pass;
@Equals(property="pass")
private String passVerify;
}
我喜欢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;
}
我喜欢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;
}
使用Hibernate Validator 4.1.0。最后,我建议使用@ScriptAssert。除了它的JavaDoc:
脚本表达式可以用任何脚本或表达式编写
JSR 223(“java平台脚本编写”)
兼容的引擎可以在类路径中找到。
注意:评估是由运行在Java VM中的脚本“引擎”执行的,因此是在Java“服务器端”,而不是在某些注释中所述的“客户端”。
例子:
@ScriptAssert(lang = "javascript", script = "_this.passVerify.equals(_this.pass)")
public class MyBean {
@Size(min=6, max=50)
private String pass;
private String passVerify;
}
或者使用更短的别名和null安全:
@ScriptAssert(lang = "javascript", alias = "_",
script = "_.passVerify != null && _.passVerify.equals(_.pass)")
public class MyBean {
@Size(min=6, max=50)
private String pass;
private String passVerify;
}
或者使用Java 7+ null-safe Objects.equals():
@ScriptAssert(lang = "javascript", script = "Objects.equals(_this.passVerify, _this.pass)")
public class MyBean {
@Size(min=6, max=50)
private String pass;
private String passVerify;
}
尽管如此,自定义类级验证器@Matches解决方案并没有什么问题。
我建议你另一种可能的解决办法。也许不那么优雅,但更容易!
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。