LoginSignup
6
3

More than 5 years have passed since last update.

Hibernate Validator(Bean Validation)のメッセージにアノテーション属性以外の値を渡す方法(埋め込む方法)

Last updated at Posted at 2018-09-15

Bean Validationのエラーメッセージには、以下のように制約アノテーションに指定した属性値を埋め込むことができます。(なお、本エントリーで扱う内容は、Hibernate Validatorを使う前提になります)

制約アノテーションの指定例
@Size(min = 4, max = 16)
private String foo;
バリデーション実行例
// ...
Bean bean = new Bean();
bean.foo = "123";
Set<ConstraintViolation<Bean>> violations = vb.validate(bean);
// ...

上記のケースで生成されるエラーメッセージは、「size must be between 4 and 16」になります。

アノテーション属性値以外を埋め込みたいと思った背景は・・・

今携わっている案件で、入力チェック時に参照する基準値を外部設定(実際にはプロパティファイル)から取得する必要性に迫られました。Bean Validationを使わずロジックでチェックするという選択肢もありましたが、入力チェックはできるだけBean Validationの仕組みを使って行いたかったので・・・Bean Validationで実現する方法を調査しました。

具体的にはどんな制約アノテーションを作ったの?

実際に作成したものではありませんが、「指定日数以降の日付であること」をチェックするアノテーションを作成し、「指定日数」の部分をプロパティファイルから取得するような制約アノテーションを作成することにしました。

制約アノテーションの作成例
@DaysLater(days = "foo.daysLater", defaultValue = 5)
private LocalDate fooDate;
プロパティファイルの設定例
foo.daysLater=10
src/main/resources/ValidationMessages.propertiesの設定例
com.example.validation.DaysLater.message = must be {days} days later

チェックはできたがメッセージが残念・・・

制約違反した際のエラーメッセージは「must be 10 days later」としたいところですが・・・残念ながらデフォルトの動作では「must be foo.daysLater days later」となってしまいます。ま〜当然の結果なのですが・・・

Bean Validation仕様準拠で対応可能か?

ちゃんと調べていませんが・・・ざっと仕様書を見た感じだとBean Validationの仕様内で対応する方法はなさそうでした。ただ・・・メッセージを組み立てる際にEL式が使えるので・・・EL式を使いこなせば対応できるかもしれません。

Hibernate Validatorの拡張機能を使えば対応できる!

Hibernate Validationが提供している拡張機能を使うと、メッセージ定義から参照できる値を変更することができます。


private int value; // アノテーションの属性値(プロパティキー)からプロパティ値を格納

// ...

public boolean isValid(LocalDate targetValue, ConstraintValidatorContext context) {

  boolean result = // チェック内容は省略...

    // チェックNGの場合はデフォルト動作を無効化し、任意の制約違反情報を生成する
  if (!result) {
    // Hibernate Validatorの拡張機能を使うため、Hibernate Validator提供のインタフェースに変換
    HibernateConstraintValidatorContext hibernateContext =
        context.unwrap(HibernateConstraintValidatorContext.class);

    // デフォルトの制約違反情報の生成を無効化
    hibernateContext.disableDefaultConstraintViolation();

    // 任意の制約違反情報を生成
    hibernateContext
        // メッセージ定義で{value}で値を参照できるようにする
        .addMessageParameter("days", this.days)
        // メッセージ定義内のEL式でvalueという変数名で値を参照できるようにする
        .addExpressionVariable("days", this.days)
        // メッセージのテンプレートはアノテーションに指定しているテンプレートを使用する(デフォルト動作と同じ)
        .buildConstraintViolationWithTemplate(hibernateContext.getDefaultConstraintMessageTemplate())
        // 制約違反情報を追加する
        .addConstraintViolation();
  }
  return result;
}

上記のような実装にすることで、メッセージにプロパティキーではなく、プロパティキーに対応する値を埋め込むことができるようになります。この仕組みを活用すると、メッセージの中に「チェックがOKとなる日付」を埋め込むこともできるようになります。

public boolean isValid(LocalDate targetValue, ConstraintValidatorContext context) {

  LocalDate validMinDate = LocalDate.now().plusDays(value);
  boolean result = // チェック内容は省略...

  if (!result) {
    HibernateConstraintValidatorContext hibernateContext =
        context.unwrap(HibernateConstraintValidatorContext.class);
    hibernateContext.disableDefaultConstraintViolation();
    hibernateContext
        .addMessageParameter("days", this.days)
        .addExpressionVariable("days", this.days)
        // チェックOKになる日付を追加
        .addMessageParameter("validMinDate", validMinDate)
        .addExpressionVariable("validMinDate", validMinDate)
        .buildConstraintViolationWithTemplate(hibernateContext.getDefaultConstraintMessageTemplate())
        .addConstraintViolation();
  }
  return result;
}
src/main/resources/ValidationMessages.properties
com.example.validation.DaysLater.message = must be {validMinDate} or later

制約違反した際のエラーメッセージは「must be 2018-09-26 or later」となり、より直感的なメッセージにすることができます。

まとめ

Hibernate Validator上で動かすことが前提条件となっているのであれば、今回紹介した拡張機能を使うことでユーザビリティの高いメッセージを組み立てやすくなると思われます。今携わっている案件はHivernate Validatorでの実行を前提として良いので、この仕組みを有効に活用しようと思っています。

6
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
3