使用Hibernate Validator进行交叉字段验证(JSR 303)
在Hibernate Validator 4.x中是否有实现(或第三方实现)的交叉字段验证? 如果不是,那么实现交叉字段验证器的最简洁方法是什么?
例如,如何使用API来验证两个bean属性是否相等(如验证密码字段与密码验证字段匹配)。
在注释中,我希望如下所示:
public class MyBean {
  @Size(min=6, max=50)
  private String pass;
  @Equals(property="pass")
  private String passVerify;
}
每个字段约束应该由一个独特的验证器注释来处理,或者换句话说,并不建议实践将一个字段的验证注释检查与其他字段进行比较; 应在班级进行跨领域验证。 此外,JSR-303第2.2部分首选通过注释列表来表示同一类型的多个验证。 这允许每个匹配指定错误消息。
例如,验证通用表单:
@FieldMatch.List({
        @FieldMatch(first = "password", second = "confirmPassword", message = "The password fields must match"),
        @FieldMatch(first = "email", second = "confirmEmail", message = "The email fields must match")
})
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 constraints;
import constraints.impl.FieldMatchValidator;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.TYPE;
import java.lang.annotation.Retention;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Target;
/**
 * Validation annotation to validate that 2 fields have the same value.
 * An array of fields and their matching confirmation fields can be supplied.
 *
 * Example, compare 1 pair of fields:
 * @FieldMatch(first = "password", second = "confirmPassword", message = "The password fields must match")
 * 
 * Example, compare more than 1 pair of fields:
 * @FieldMatch.List({
 *   @FieldMatch(first = "password", second = "confirmPassword", message = "The password fields must match"),
 *   @FieldMatch(first = "email", second = "confirmEmail", message = "The email fields must match")})
 */
@Target({TYPE, ANNOTATION_TYPE})
@Retention(RUNTIME)
@Constraint(validatedBy = FieldMatchValidator.class)
@Documented
public @interface FieldMatch
{
    String message() default "{constraints.fieldmatch}";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
    /**
     * @return The first field
     */
    String first();
    /**
     * @return The second field
     */
    String second();
    /**
     * Defines several <code>@FieldMatch</code> annotations on the same element
     *
     * @see FieldMatch
     */
    @Target({TYPE, ANNOTATION_TYPE})
    @Retention(RUNTIME)
    @Documented
            @interface List
    {
        FieldMatch[] value();
    }
}
验证器:
package constraints.impl;
import constraints.FieldMatch;
import org.apache.commons.beanutils.BeanUtils;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class FieldMatchValidator implements ConstraintValidator<FieldMatch, Object>
{
    private String firstFieldName;
    private String secondFieldName;
    @Override
    public void initialize(final FieldMatch constraintAnnotation)
    {
        firstFieldName = constraintAnnotation.first();
        secondFieldName = constraintAnnotation.second();
    }
    @Override
    public boolean isValid(final Object value, final ConstraintValidatorContext context)
    {
        try
        {
            final Object firstObj = BeanUtils.getProperty(value, firstFieldName);
            final Object secondObj = BeanUtils.getProperty(value, secondFieldName);
            return firstObj == null && secondObj == null || firstObj != null && firstObj.equals(secondObj);
        }
        catch (final Exception ignore)
        {
            // ignore
        }
        return true;
    }
}
我建议你另一个可能的解决方案 也许不那么优雅,但更容易!
public class MyBean {
  @Size(min=6, max=50)
  private String pass;
  private String passVerify;
  @AssertTrue(message="passVerify field should be equal than pass field")
  private boolean isValid() {
    return this.pass.equals(this.passVerify);
  }
}
isValid()方法由验证器自动调用。
我很惊讶这不是开箱即用的。 无论如何,这是一个可能的解决方案。
我创建了一个类级别验证程序,而不是原始问题中描述的字段级别。
以下是注释代码:
package com.moa.podium.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 "{com.moa.podium.util.constraints.matches}";
  Class<?>[] groups() default {};
  Class<? extends Payload>[] payload() default {};
  String field();
  String verifyField();
}
验证者本身:
package com.moa.podium.util.constraints;
import org.mvel2.MVEL;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class MatchesValidator implements ConstraintValidator<Matches, Object> {
  private String field;
  private String verifyField;
  public void initialize(Matches constraintAnnotation) {
    this.field = constraintAnnotation.field();
    this.verifyField = constraintAnnotation.verifyField();
  }
  public boolean isValid(Object value, ConstraintValidatorContext context) {
    Object fieldObj = MVEL.getProperty(field, value);
    Object verifyFieldObj = MVEL.getProperty(verifyField, value);
    boolean neitherSet = (fieldObj == null) && (verifyFieldObj == null);
    if (neitherSet) {
      return true;
    }
    boolean matches = (fieldObj != null) && fieldObj.equals(verifyFieldObj);
    if (!matches) {
      context.disableDefaultConstraintViolation();
      context.buildConstraintViolationWithTemplate("message")
          .addNode(verifyField)
          .addConstraintViolation();
    }
    return matches;
  }
}
请注意,我使用MVEL来检查被验证对象的属性。 这可以用标准反射API来替代,或者如果它是您正在验证的特定类,则访问器方法本身。
然后@Matches注释可以在bean上使用,如下所示:
@Matches(field="pass", verifyField="passRepeat")
public class AccountCreateForm {
  @Size(min=6, max=50)
  private String pass;
  private String passRepeat;
  ...
}
作为一个免责声明,我在过去的5分钟内写了这个,所以我可能还没有解决所有的bug。 如果出现任何问题,我会更新答案。
链接地址: http://www.djcxy.com/p/61087.html上一篇: Cross field validation with Hibernate Validator (JSR 303)
