Bean Validationで独自のアノーテションを作成し、検証を行う
JavaにはBean Validationというものがあります。フィールドに@NotNullなどと付けておくアレです。JSR303と呼ばれています。今日はこの話です。
Bean Validationですが、クラスのフィールドに@Hogeとか付けておいて、後はフレームワークに勝手に検証させるなり、自分でvalidするなりして利用するかと思います。
これ、実際に使ってみると確かに便利で良いのですが、自分のビジネスロジックのニーズにマッチするアノテーションが無い!ということが割と頻繁に発生します。そこで、独自のアノテーションを作ってしまおう!というのが今回のメインの話です。
参考情報
JSR303とは何か、Bean Validationが何かを知りたい人は以下が参考になるかと思います。
JSR 303 Bean Validationで遊んでみるよ! – Yamkazu’s Blog
上記のページにもありますが、独自アノテーションを作るなら以下が参考になります。ISBNとか良い題材ですね。
独自アノテーションの作り方
さて、それではいよいよ独自アノテーションを作って行こうと思います。今回はByteSizeというアノテーションを作って検証してみようと思います。
既にSizeというアノテーションが存在しますが、これは文字列に付けた場合length、つまり文字数で判定するので、文字のバイト数は関係が無いという状況です。今回はバイト数をチェックするためのアノテーションを作って行きます。
まずはアノテーション。
import javax.validation.Constraint; import javax.validation.Payload; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.Target; import static java.lang.annotation.ElementType.FIELD; import static java.lang.annotation.RetentionPolicy.RUNTIME; @Documented @Constraint(validatedBy = {ByteSizeValidator.class}) @Target({FIELD}) @Retention(RUNTIME) public @interface ByteSize { String message() default "{validation.ByteSize.message}"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; /** * 文字サイズを計測する文字エンコーディング * * @return */ String encoding() default "UTF-8"; /** * 許容するバイトサイズの最大値 * * @return */ int size(); @Target({FIELD}) @Retention(RUNTIME) @Documented public @interface List { ByteSize[] value(); } }
アノテーションに対する実際の検証処理はこちらです。
import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext; import java.io.UnsupportedEncodingException; public class ByteSizeValidator implements ConstraintValidator<ByteSize, String> { /** * 文字エンコーディング */ private String encoding; /** * 許容する最大バイト数 */ private int size; @Override public void initialize(ByteSize byteSize) { encoding = byteSize.encoding(); size = byteSize.size(); } @Override public boolean isValid(String value, ConstraintValidatorContext context) { if (value == null) { return true; } return isByteSizeValid(value); } /** * 引数で指定した文字列のバイトサイズを検証します。 * * @param value 検証する文字列 * @return trueの場合、指定した許容サイズ以下 */ private boolean isByteSizeValid(String value) { try { byte[] bytes = value.getBytes(encoding); if (bytes.length > size) { //大きい return false; } } catch (UnsupportedEncodingException e) { return false; } return true; } }
こんな感じで作っておくと分かりやすいコードになると思います。
使い方はフレームワークに検証させるか、自分でValidator操作して検証するかします。
解説
ByteSizeインタフェース(アノテーション)ですが、何やらやたら沢山アノテーションが付いています。
@Documented アノテーション用のアノテーションです。これを付けると、アノテーションが付いていることがJavadocに載ります。例えば今回作った@ByteSizeアノテーションをHogeクラスのnameフィールドに付けてHogeクラスをJavadoc出力したとします。するとnameには@ByteSizeがついてることがJavadocに載ります。そういうことをするよ、というアノテーションが@Documentedです。
@Constraint これはそのままですね。ConstraintValidatorの実装クラスオブジェクトを指定しています。
@Target アノテーションをどこに付けるか、です。今回の@ByteSizeアノテーションはフィールドにだけ付けられれば良いのでFIELDだけ指定しています。
@Retention 保持。アノテーションの情報をどこまで持たせておくか、です。RUNTIMEを指定しているので実行時まで保持しておきます。これについては少々古いですが、このサイトの説明が丁寧かと思います。
以上です。
今後もバリデーション力を高めて行きたいです。