JSR 303はJava Beanオブジェクトのためのバリデーション機構を実現する目的で発足し、実装はJDK1.6(1.5?)から。
JSRの解説になってしまいますが、ServletもJSRで定義されており、BeanValidationもServlet同様にAPIの概要(Java的表現でいうとインタフェース)を定義したもの。定義後しばらく放置。その後誰かが作成した実装を標準実装に取り込むことがあります。
今回のBeanValidationではHibernateValidatorをJavaの標準実装としています。SpringにおけるValidation機能もHibernateを利用しています。他の実装で知名度が高いものは「Apache Foundation」「Jakarta Commons」の「Commons Validator」が存在します。
Bean Validationでは以下のようなアノテーションが用意されています。後述しますが自作も可能です。
No. | 名称 | 概要 |
---|---|---|
1 | @Null, @NotNull | 項目がnullである(もしくはnullでない)ことを検証 |
2 | @Digits | 項目が数値であり設定された範囲内 |
3 | @DecimalMin, @DecimalMax, @Min, @Max | 項目が数値であり、設定値より小さい(大きい) |
4 | @Future, @Past | 項目が日付であり設定値より未来(過去) |
5 | @Pattern | 項目が正規表現のパターンにマッチしている |
6 | @Size | 項目が設定値のサイズに収まっている |
7 | @AssertTrue, @AssertFalse | 項目がTrue(False) |
* @Null, @NotNull 以外は、項目がnullの時には実行されません。
Bean Validationではこちらのソースのように実装できます。アノテーションを用いて実装します。インタフェースにも記述可能です。
Validator#validateにて第1引数は精査するインスタンス、第2引数(以降も)は精査ルールの記述してあるインタフェースを指定します。この時第2引数を指定しなければ精査するインスタンスのクラス(implementsのインタフェースも)の精査を実行。第2引数を指定していればそのインタフェースに記述されている精査のみを実行します(精査対象のインスタンスが、指定したインタフェースをimplementsしていなければなりません)。
public class PersonImpl
implements Serializable {
@NotNull // Fieldに記述可能
private String personId;
private String personName;
public String getPersonId(){
return this.personId;
}
@NotNull // メソッドに記述可能
public String getPersonName(){
return this.personName;
}
}
public interface IPerson
implements Serializable {
@NotNull(message="id.required")
public String getPersonId();
// 複数可
@NotNull(message="name.required")
@Size(min=10,max=50
,message="name.len {min}_{max}")
public String getPersonName();
}
public class PersonImpl
implements IPerson {
// 内容は省略
}
IPerson dto = new PersonImpl();
dto.setPersonName("abc");
ValidatorFactory f = Validation.buildDefaultValidatorFactory();
Validator v = f.getValidator();
Set<ConstraintViolation<IPerson>> violations = v.validate(dto,
IPerson.class); // インタフェースを複数指定可
for (ConstraintViolation<IPerson> violation : violations) {
System.out.print(violation.getPropertyPath());
System.out.print("[");
System.out.print(violation.getInvalidValue());
System.out.print("]=");
System.out.print(violation.getMessage());
System.out.print("[");
System.out.print(violation.getMessageTemplate());
System.out.println("]");
}
もしくは
Set<ConstraintViolation<IPerson>> violations = v.validate(dto);
こちらの場合PersonImplに記述されている精査を実施
personId[null]=id.required[id.required]
personName[abc]=name.len 10_50[name.len {min}_{max}]
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.FIELD,
ElementType.ANNOTATION_TYPE,
ElementType.CONSTRUCTOR, ElementType.PARAMETER })
@Constraint(validatedBy = {})
@NotNull(message = "validate.personId.required")
@Size(min = 12, max = 12
, message = "validate.personId.size")
@Pattern(regexp = "[0-9]*"
, message = "validate.personId.invalid")
public @interface APersonId {
String message() default "validate.personId";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.FIELD,
ElementType.ANNOTATION_TYPE,
ElementType.CONSTRUCTOR,
ElementType.PARAMETER })
@interface List {
APersonId[] value();
}
}
ID(12文字,数字のみ)を組み合わせで実装してみました。
コチラのアノテーションは「APersonId」という名称なので利用する際は「@APersonId」と記述します。フィールド、メソッド共に記述可能です。
@Documented
@Constraint(validatedBy = PhoneValidator.class)
@Target({ ElementType.METHOD, ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface APhone {
String message() default "validate.phone";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
boolean onlyNumber() default false;
public class PhoneValidator implements
ConstraintValidator<APhone, String> {
public void initialize(APhone aPhone) {
this.onlyNumber = aPhone.onlyNumber();
}
private boolean onlyNumber;
public boolean isValid(String phone,
ConstraintValidatorContext context) {
if (phone == null) {
return true;
}
if (onlyNumber) {
return phone.matches("[0-9]*");
} else {
return phone.matches("[0-9()-]*");
}
}
}
}
電話番号を自作してみました。既存の組み合わせでなく、自作精査を行うには「@Constraint」アノテーションの「validatedBy」にてクラスを指定します。「validatedBy」には1つだけクラスを指定します。今回はインナークラスとしimplements ConstraintValidator しました。
初期値はinitializeメソッドで取得しisValidの第1引数で精査する値を取得できるようです。
自作すると考えられる処理は「相関項目チェック(パスワードと確認用等)」や「業務ロジック(未使用のIDかどうか等)」といった処理を記述することはできそうだが、現時点では不明。
package com.hogehoge;
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import javax.validation.Payload;
import com.hogehoge.Same.ConfirmValidator;
@Documented
@Constraint(validatedBy = { ConfirmValidator.class })
@Target({ TYPE, ANNOTATION_TYPE })
@Retention(RUNTIME)
public @interface Same {
public class ConfirmValidator implements
ConstraintValidator<Same, Object> {
private String field1; // 項目名1
private String field2; // 項目名2
private String message;
public void initialize(Same constraintAnnotation) {
message = constraintAnnotation.message();
field1 = constraintAnnotation.field1();
field2 = constraintAnnotation.field2();
}
public boolean isValid(Object value,
ConstraintValidatorContext context) {
Object val1 = field1を用いて値を取得する処理;
Object val2 = field2を用いて値を取得する処理;
if (val1.equals(val2)) {
return true;
} else {
context.disableDefaultConstraintViolation();
context
.buildConstraintViolationWithTemplate
(message).addConstraintViolation();
return false;
}
}
}
@Target({ TYPE, ANNOTATION_TYPE })
@Retention(RUNTIME)
@Documented
public @interface List {
Same[] value();
}
String field1();
String field2();
Class<?>[] groups() default {};
String message() default "{validate.diff}";
Class<? extends Payload>[] payload() default {};
}
package com.hogehoge;
@Same(message = "{diff.confirm}",
field1 = "password",
field2 = "confirmPassword")
public class PasswordImpl implements Serializable {
private String confirmPassword;
private String password;
// 不要なプロパティやgetter等は省略
}
diff.confirm=パスワードとパスワード(確認)が一致しません。
強引ではありますが、項目間精査のアノテーションを作成しました。
項目間精査なので「@Target({ TYPE, ANNOTATION_TYPE })」にしました。
よりよい方法がある場合、教えてください。