[Java] Bean validation 1.1(JSR303, JSR349)の拡張

Javaでオブジェクトをバリデーションする仕様にBean validationがあります。
デフォルトのBean validationでは、”nullでないこと””桁数が9桁以上であること”など、最小単位のバリデーションしか用意されていませんので、ある程度複雑なアプリケーションを開発する場合、独自のバリデーションを追加する必要に迫られます。
この記事ではBean vaidationを使った独自のバリデーションを作る方法を紹介します。

Bean validation 1.1(JSR303, JSR349) とは

以下に仕様がまとめられています。

リンク先より抜粋
  • アノテーションでオブジェクトの制約を表現できる
  • 独自の制約を追加することができる
  • オブジェクト(ネストしたオブジェクトを含む)をバリデーションするAPIを提供している
  • メソッド、コンストラクターの引数、戻り値を検証するAPIを提供している
  • (ローカライズされた)バリデーションエラーを取得できる
  • Java EEに統合されている(Java EE以外でも使える)

例: コード1


作ったバリデーションはどのF/W(Java EE, Spring, Hibernate, ...etc)からでも共通的に使うことができます。

まずは、サンプルとしてSpringからBean validationを使った例でBean validationの用法を確認した上で、独自のバリデーションを定義していきます。


Bean validationの用法紹介: Spring Controllerの引数を検証するために使う

例としてDocumentオブジェクトを保存するAPIを用意します。
(前提: SpringとBean validationは事前に使える状態になっていること)

コード2: DocumentController.java

コード3: Document.java


上のコードの説明
Validアノテーション(Bean Validationが提供)
このアノテーションを付与すると、引数 doc に対してバリデーションが実行されます。

BindingResultクラス(Springが提供)
BindingResultを引数に指定しておくと、Springがバリデーション結果(エラーのあったフィールド名、満たせなかった制約、エラーメッセージ)をこのオブジェクトに設定して渡してくれる。もし、BindingResultを引数に指定しなかった場合、例外(Springの場合: MethodArgumentNotValidException.class)がThrowされるので、Springの@ExceptionHandlerとかを使ってハンドルすれば共通的にバリデーションエラー時の挙動を記述できます。

CustomConstraintアノテーション(独自に定義)
このプログラムに合わせてカスタマイズしたバリデーションを実施します。
(* 後ほど説明します)

NotNullアノテーション(Bean Validationが提供)
textに設定された値がnullの場合にバリデーションエラーになります。

⑤ ネストしたオブジェクト(配列、Listに対しても付与可能)に付与したValidアノテーション(Bean Vaildationが提供)
Validアノテーションを付けるとネストしたオブジェクトに対してもバリデーションが実施されます。

利用側からするとアノテーションを付与するだけになるため、コード簡潔に保つことができています。この例でいうと、CustomConstraintという独自のアノテーションを使っていますがこのようなアノテーションを共通的に用意しておけばバリデーションロジックを汎用的に用意しておくことができるため開発の効率化に繋がります。

それでは次にDocumentオブジェクトのidフィールドに対して、独自の制約(CustomConstraintアノテーション)を導入してみましょう

独自の制約を作成する

上のコード3にある@CustomConstraintアノテーションを作ります。
これから見ていくサンプルを簡単に説明すると、CustomConstraintアノテーションのpayloadフィールドにバリデーション内容(Validatorインターフェースの実装クラス)を指定しておくとそのバリデーション内容が実行されるというものです

コード4: CustomConstraint.java

コード5: CustomValidator.java

コード6: Validators.java
コード7: Validator.java
コード8: Message.java

コード9: WebConfig.java
上のコードの説明
独自の制約を作るときにはConstraintアノテーションを付与したアノテーション(コード4)を用意します。バリデーションロジックは、ConstraintアノテーションのvalidateByに指定したクラス(コード5)に記述します。

またこのアノテーションには、以下3フィールドを定義しておく必要があります。

  • message 制約に違反したときにこのメッセージが返されます。このメッセージにはローカライズ等の変換処理が施され返されます。変換に関する仕様はこちらに書かれています。 仕様ではResourceBundleにあるValidationMessages(ValidationMessages.properties)がメッセージの変換に使われますが、独自のメッセージ定義を使いたいときには、Springの場合、コード9に記載のあるようにWebConfigurerAdaptor#getValidatorをoverrideし独自の設定をすることでこれが実現できます。
  • groups groupsを指定した場合、バリデーションする/しないを任意の単位で分割することができます。 例えば、「あるAPI Aではバリデーションしたいけど、別のAPI Bではバリデーションしたくない」といったときにAPI Aグループと、API Bグループをつくり、バリデーションするときにこのグループを指定すれば、指定したグループ単位のバリデーションが実施されます。(* Springでグループ単位のバリデーションをするには、javax.validation.Validアノテーションの代わりに、org.springframework.validation.annotation.Validatedアノテーションを使います)
  • payload 制約アノテーション(e.g. NotNull)を付与するときに、制約に対して更に情報を付与したいときに使用します。 例えば、フィールドA、BにNotNullアノテーションを付けていて、フィールドAがnullのときはエラーとして扱い、フィールドBがnullのときは警告として扱いたいといったときに、フィールドAには@NotNull(payload=sample.Error.class)、フィールドBには@NotNull(payload=sample.Warning.class)を指定することで、バリデーションエラー受取側では、このpayloadに設定された値によって挙動を変えることができます。


実際のバリデーションはコード5の①で実施されています。
この例では、payloadに指定されたバリデーター(e.g. Validators.Id)に定義されたバリデーションロジックを実施し、エラーがあればメソッドの引数のConstraintValidatorContextにメッセージを追加します。
以上でBean validationを拡張した独自のバリデーションが完成しました。


Author
Yuki Sanada

Leave a Reply

Popular Posts