Spring MVCというか、JSR-303で値の比較を行うヴァリデーション

ユーザ登録における、[パスワード]と[パスワードの再確認]欄の一致とかに使うやつを自分で用意する方法について(・ω・)

実装

アノテーションと実装はこんな感じのものを用意。

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

    String message() default "{smart.extension.spring.validator.FieldCompare.message}";

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

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

    String field();

    String compareTo();

    @Target({ TYPE, ANNOTATION_TYPE })
    @Retention(RUNTIME)
    @Documented
    @interface List {

        FieldCompare[] value();
    }
}
public class FieldCompareValidator implements ConstraintValidator<FieldCompare, Object> {

    private String fieldFieldName;

    private String compareToFieldName;

    @Override
    public void initialize(final FieldCompare constraintAnnotation) {
        this.fieldFieldName = constraintAnnotation.field();
        this.compareToFieldName = constraintAnnotation.compareTo();
    }

    @Override
    public boolean isValid(final Object value, final ConstraintValidatorContext context) {
        final BeanWrapper wrapper = PropertyAccessorFactory.forBeanPropertyAccess(value);
        final Object fieldValue = wrapper.getPropertyValue(this.fieldFieldName);
        final Object compareToValue = wrapper.getPropertyValue(this.compareToFieldName);
        boolean isValid = ((fieldValue == null) && (compareToValue == null)) || ((fieldValue != null) && (fieldValue.equals(compareToValue)));

        if (!isValid) {
            context.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate()).addNode(this.fieldFieldName).addConstraintViolation()
                    .disableDefaultConstraintViolation();
        }

        return isValid;
    }
}

サンプル

例えば検証対象のモデルに下記の様な感じで設定。

@FieldCompare(field = "newPasswordConfirm", compareTo = "newPassword")
public class PasswordViewModel implements Serializable {

    @NotEmpty
    private String newPassword;

    private String newPasswordConfirm;

...
}
<ul>
<li>
<form:password path="newPassword"/>
<form:errors path="newPassword" cssClass="error" />
</li>
<li>
<form:password path="newPasswordConfirm"/>
<form:errors path="newPasswordConfirm" cssClass="error" />
</li>
</ul>

ポイントは、入力欄のエラーとして扱う方のメンバをfieldで指定すること。
この場合、パスワードが一致しなかった時のエラーはnewPasswordConfirmの方に表示されます。
FieldCompareValidatorのcontext.buildConstraintViolationWithTemplate()のあたりでやっているのがこの設定です(・ω・)


これ、どっかのサイトを参考にしたものですが、出典は忘れました(・∀・;)