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 })」にしました。
よりよい方法がある場合、教えてください。