カスタムバリデーションの実装方法

バリデーションの基本的な使用方法は、以下の記事参照

olafnosuke.hatenablog.com

提供されているアノテーション以外のバリデーションを行う場合、独自でバリデーション処理を実装する必要がある。

単項目チェックのカスタムバリデーションの実装方法

「文字列のバイト数チェック」を追加する例を示す。参考

アノテーションの作成

  1. @Targetでアノテーションの付与対象を定義する。
    例えば、ElementType.FIELDを指定した場合、このアノテーションはフィールドにのみ付与可能となる。
    コンマ区切りで複数定義も可能。
  2. @Retentionでアノテーションが影響する範囲を定義する。
    RUNTIMEを指定した場合、 実行時に参照できるようになる。
  3. @Constraintで具体的な処理が記述されたクラスを指定する。
  4. @Document(対象となるアノテーションにより付加した情報が javadoc に反映されなければならないことを示すマーカーアノテーション)を付与する。
  5. message(), groups(), payLoad(), List { A value() }を追加する。

    項目 説明
    message() エラー時のメッセージ。ここに直接メッセージを記述するか、プロパティのキーを指定する。
    groups() バリデーショングループのカスタマイズに使用。
    payLoad() チェック対象のオブジェクトになんらかのメタ情報を与えるための宣言。
    List { A value() } チェック可能な対象(複数可能)を定義する。
  6. バリデーションに使用するための属性を追加する。例ではcharset(), max(), min()を追加。 defaultでデフォルト値を設定することができる。
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

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

@Target({ ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(validatedBy = { ByteLengthValidator.class })
public @interface ByteLength {

    /** キャラセット */
    String charset() default "UTF-8";

    /** 最大値 */
    int max() default Integer.MAX_VALUE;

    /** 最小値 */
    int min() default 0;

    String message() default "{jp.co.sample.webapp.validation.ByteLength.message}";

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

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

    @Target({ ElementType.FIELD })
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @interface List {
        Alphanumeric[] value();
    }
}

具体的なロジックを記述したクラスの作成

  1. javax.validation.ConstraintValidator<A extends Annotation, T>を実装したクラスを作成する。
    Aにはアノテーションの型、Tにはアノテーションがバリデーションの対象とする型を指定する。
  2. initializeメソッドをオーバーライドし、アノテーションの属性値を取得する。
  3. isValidメソッドで、具体的な処理を記述する。
import java.nio.charset.Charset;

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

public class ByteLengthValidator implements ConstraintValidator<ByteLength, String> {

    /** エラー時のメッセージ */
    String message;

    /** キャラセット */
    String charset;

    /** 最大値 */
    int max;

    /** 最小値 */
    int min;

    @Override
    public void initialize(ByteLength constraintAnnotation) {
        message = constraintAnnotation.message();
        charset = constraintAnnotation.charset();
        max = constraintAnnotation.max();
        min = constraintAnnotation.min();
    }

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        int length = value.getBytes(Charset.forName(charset)).length;
        return min <= length && length <= max;
    }
}

相関チェックのカスタムバリデーションの実装方法

2つの日付(from, to)の整合性を検証するバリデーションを作成する例を示す。
作成するクラスは単項目チェックの場合と同様で、アノテーションとその具体的な処理を記述するクラスの2つである。

アノテーションの作成

  1. @Targetでアノテーションの付与対象を定義する。フィールドにアノテーションを付与しても他のフィールドの値を参照することができない。
    そのため、相関チェックではクラスにアノテーションを付与することになるのでElementType.TYPEを指定する。
  2. @Retentionでアノテーションが影響する範囲を定義する。 RUNTIMEを指定した場合、 実行時に参照できるようになる。
  3. @Constraintで具体的な処理が記述されたクラスを指定する。
  4. @Document(対象となるアノテーションにより付加した情報が javadoc に反映されなければならないことを示すマーカーアノテーション)を付与する。
  5. message(), groups(), payLoad(), List { A value() }を追加する。

    項目 説明
    message() エラー時のメッセージ。ここに直接メッセージを記述するか、プロパティのキーを指定する。
    groups() バリデーショングループのカスタマイズに使用。
    payLoad() チェック対象のオブジェクトになんらかのメタ情報を与えるための宣言。
    List { A value() } チェック可能な対象(複数可能)を定義する。
  6. バリデーションに使用するための属性を追加する。 クラスからチェックに必要なフィールドの値を取得する必要があるため、対象のフィールドを指定する属性を追加する。
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

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

@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(validatedBy = { DateCorrelationValidValidator.class })
public @interface DateCorrelationValid {
    String message() default "{jp.co.sample.webapp.validation.DateCorrelationValid.message}";

    /** from */
    String fromDateFieldName();

    /** to */
    String toDateFieldName();

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

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

    @Target({ ElementType.TYPE })
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @interface List {
        DateCorrelationValid[] value();
    }
}

具体的なロジックを記述したクラスの作成

  1. javax.validation.ConstraintValidator<A extends Annotation, T>を実装したクラスを作成する。 Aにはアノテーションの型、Tにはアノテーションがバリデーションの対象とする型を指定する。
  2. initializeメソッドをオーバーライドし、アノテーションの属性値を取得する。
  3. isValidメソッドで、具体的な処理を記述する。アノテーションを付与したクラスからフィールドの値を取得するためにBeanWrapperを用いる。
  4. クラスにアノテーションを付与する場合、バリデーションエラー時のメッセージはフィールドには紐づかず、グローバルエラーとなる。context.disableDefaultConstraintViolation();以降の記述で、メッセージをフィールドと紐づけている。
import java.time.LocalDate;

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

import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;

public class DateCorrelationValidValidator implements ConstraintValidator<DateCorrelationValid, Object> {

    /** メッセージ */
    String message;

    /** fromのフィールド名 */
    String fromDateFieldName;

    /** toのフィールド名 */
    String toDateFieldName;

    @Override
    public void initialize(DateCorrelationValid constraintAnnotation) {
        message = constraintAnnotation.message();
        fromDateFieldName = constraintAnnotation.fromDateFieldName();
        toDateFieldName = constraintAnnotation.toDateFieldName();
    }

    @Override
    public boolean isValid(Object value, ConstraintValidatorContext context) {
        BeanWrapper beanWrapper = new BeanWrapperImpl(value);
        LocalDate from = (LocalDate) beanWrapper.getPropertyValue(fromDateFieldName);
        LocalDate to = (LocalDate) beanWrapper.getPropertyValue(toDateFieldName);

        if (from.isBefore(to)) {
            return true;
        }

        // デフォルトのエラーメッセージを使用しない
        context.disableDefaultConstraintViolation();
        // エラーメッセージの設定
        context.buildConstraintViolationWithTemplate(message).addPropertyNode(toDateFieldName)
                .addConstraintViolation();
        return false;
    }

}