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を指定しているので実行時まで保持しておきます。これについては少々古いですが、このサイトの説明が丁寧かと思います。
以上です。
今後もバリデーション力を高めて行きたいです。