Springで独自のバリデータを作成する

相関チェック用のバリデータの作成方法について。

Springには相関チェック用のバリデータが用意されていないため、自分で独自のバリデータを作成する必要があります。

環境

  • Spring Boot: 2.3.3.RELEASE

作成方法

今回は属性に指定されたフィールドのうちどれか1つでも入力されていればOKとみなす@NotAllBlankというアノテーションを作成しました。
ソースコードの内容はほぼ参考書の通りです。
以下に記載するソースコード内で番号付きのコメントの部分以外はコピペで動作可能でした。

アノテーション作成

@Documented
@Constraint(validatedBy = {NotAllBlankValidator.class})     // ①
@Target({ ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface NotAllBlank {

	String message() default "{xyz.tenohira.magazinemanager.validation.NotAllBlank.message}";   // ②
	Class<?>[] groups() default {};
	Class<? extends Payload>[] payload() default {};
	
	// ③バリデータの属性定義
	String[] fields();
	
	@Target({ ElementType.TYPE, ElementType.ANNOTATION_TYPE})
	@Retention(RetentionPolicy.RUNTIME)
	@Documented
	public @interface List {
		NotAllBlank[] value();  // ④
	}
}

@Constraint(validatedBy = {NotAllBlankValidator.class})

@ConstraintvalidatedBy属性にバリデータの実装クラスを指定します。

String message() default "{xyz.tenohira.magazinemanager.validation.NotAllBlank.message}"

デフォルトメッセージを指定します。
ここで指定したxyz.tenohira.magazinemanager.validation.NotAllBlank.messageの内容は後述するValidationMessages.propertiesに記載します。

③ バリデータの属性定義

アノテーションに指定できる属性をここで定義します。

NotAllBlank[] value();

属性で指定された値を格納する変数を用意します。(と解釈しました)

バリデータの作成

public class NotAllBlankValidator implements ConstraintValidator<NotAllBlank, Object> {     // ①

    // ②
	private String[] fields;
	private String message;
	
	// ③バリデータ初期化
	public void initialize(NotAllBlank constraintAnnotation) {
		this.fields = constraintAnnotation.fields();
		this.message = constraintAnnotation.message();
	}
	
	// ④検証処理
	@Override
	public boolean isValid(Object value, ConstraintValidatorContext context) {
		
		BeanWrapper beanWrapper = new BeanWrapperImpl(value);
		for (String string : fields) {
			Object fieldValue = beanWrapper.getPropertyValue(string);
			if (StringUtils.hasText(fieldValue.toString())) {
				return true;
			}
		}
		
		context.disableDefaultConstraintViolation();
		context.buildConstraintViolationWithTemplate(message)
			.addPropertyNode(fields[0]).addConstraintViolation();
		return false;
	}	
}

ConstraintValidatorインターフェースの実装

型パラメータの第一引数に、作成したアノテーション名を指定します。

② 変数

アノテーションの属性名とメッセージを変数として定義します。

③ バリデータ初期化

バリデータで使用する変数を初期化します。

④ 検証処理の実装

検証の内容を記述します。
BeanWrapper.getPropertyValue(属性名の変数)により、属性に指定した値を取得できます。
この例では1つでも空でないフィールドがあればtrueを返すようにしています。
検証結果がfalseとなる場合(この例では全てのフィールドが空だった場合)、下記のようにエラーメッセージとフィールドを紐づけます。

context.buildConstraintViolationWithTemplate(message).addPropertyNode(fields[0]).addConstraintViolation();

エラーメッセージ定義

ValidationMessages.propertiessrc/main/resources直下に作成します。
以下のようにメッセージを定義します。

xyz.tenohira.magazinemanager.validation.NotAllBlank.message={fields}のどれか1つは入力必須です

使用方法

以下のように使用します。

@Data
@NotAllBlank(fields = {"section", "title"})
public class Article {

	/** セクション */
	@Length(max = 30)
	private String section;
	
	/** タイトル */
	@Length(max = 50)
	private String title;
	
	// 省略
}

2つ以上のフィールドにまたがるバリデータのため、クラスにアノテーションを付けます。
fields属性にチェックを行いたいフィールドを指定します。

参考

  • Spring徹底入門 Spring FrameworkによるJavaアプリケーション開発
    • 5.7.6 入力チェックルールの追加(225~228ページ)
    • 5.7.9 エラーメッセージの解決(231~235ページ)