在Hibernate Validator 4.x中是否有跨字段验证的实现(或第三方实现)?如果不是,实现跨字段验证器的最干净的方法是什么?

例如,如何使用API来验证两个bean属性是否相等(例如验证密码字段与密码验证字段是否匹配)。

在注释中,我期望如下内容:

public class MyBean {
  @Size(min=6, max=50)
  private String pass;

  @Equals(property="pass")
  private String passVerify;
}

当前回答

为什么不试试Oval: http://oval.sourceforge.net/呢

我看起来像它支持OGNL,所以也许你可以用更自然的方式来做

@Assert(expr = "_value ==_this.pass").

其他回答

我建议你另一种可能的解决办法。也许不那么优雅,但更容易!

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。

您需要显式地调用它。在上面的例子中,bradhouse给出了编写自定义约束的所有步骤。

在调用者类中添加此代码。

ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
validator = factory.getValidator();

Set<ConstraintViolation<yourObjectClass>> constraintViolations = validator.validate(yourObject);

在上述情况下,它将是

Set<ConstraintViolation<AccountCreateForm>> constraintViolations = validator.validate(objAccountCreateForm);

为什么不试试Oval: http://oval.sourceforge.net/呢

我看起来像它支持OGNL,所以也许你可以用更自然的方式来做

@Assert(expr = "_value ==_this.pass").

非常好的解决方案,布拉德豪斯。是否有办法将@Matches注释应用到多个字段?

编辑: 下面是我想出的解决方案来回答这个问题,我修改了约束以接受数组而不是单个值:

@Matches(fields={"password", "email"}, verifyFields={"confirmPassword", "confirmEmail"})
public class UserRegistrationForm  {

    @NotNull
    @Size(min=8, max=25)
    private String password;

    @NotNull
    @Size(min=8, max=25)
    private String confirmPassword;


    @NotNull
    @Email
    private String email;

    @NotNull
    @Email
    private String confirmEmail;
}

注释的代码:

package springapp.util.constraints;

import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.*;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import javax.validation.Constraint;
import javax.validation.Payload;

@Target({TYPE, ANNOTATION_TYPE})
@Retention(RUNTIME)
@Constraint(validatedBy = MatchesValidator.class)
@Documented
public @interface Matches {

  String message() default "{springapp.util.constraints.matches}";

  Class<?>[] groups() default {};

  Class<? extends Payload>[] payload() default {};

  String[] fields();

  String[] verifyFields();
}

以及实现:

package springapp.util.constraints;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

import org.apache.commons.beanutils.BeanUtils;

public class MatchesValidator implements ConstraintValidator<Matches, Object> {

    private String[] fields;
    private String[] verifyFields;

    public void initialize(Matches constraintAnnotation) {
        fields = constraintAnnotation.fields();
        verifyFields = constraintAnnotation.verifyFields();
    }

    public boolean isValid(Object value, ConstraintValidatorContext context) {

        boolean matches = true;

        for (int i=0; i<fields.length; i++) {
            Object fieldObj, verifyFieldObj;
            try {
                fieldObj = BeanUtils.getProperty(value, fields[i]);
                verifyFieldObj = BeanUtils.getProperty(value, verifyFields[i]);
            } catch (Exception e) {
                //ignore
                continue;
            }
            boolean neitherSet = (fieldObj == null) && (verifyFieldObj == null);
            if (neitherSet) {
                continue;
            }

            boolean tempMatches = (fieldObj != null) && fieldObj.equals(verifyFieldObj);

            if (!tempMatches) {
                addConstraintViolation(context, fields[i]+ " fields do not match", verifyFields[i]);
            }

            matches = matches?tempMatches:matches;
        }
        return matches;
    }

    private void addConstraintViolation(ConstraintValidatorContext context, String message, String field) {
        context.disableDefaultConstraintViolation();
        context.buildConstraintViolationWithTemplate(message).addNode(field).addConstraintViolation();
    }
}

我没有评论第一个答案的名声,但我想补充的是,我已经为获胜的答案添加了单元测试,并有以下观察:

如果第一个或字段名出错,就会出现验证错误,就好像值不匹配一样。不要被拼写错误绊倒。

@FieldMatch(first=“invalidFieldName1”, second=“validFieldName2”)

验证器将接受等效的数据类型,即这些都将通过FieldMatch:

private String stringField = "1"; private Integer integerField = new Integer(1) private int intField = 1;

如果字段的对象类型没有实现equals,则验证将失败。