読者です 読者をやめる 読者になる 読者になる

kagamihogeの日記

kagamihogeの日記です。

The Java EE 7 TutorialのBean Validation: Advanced Topicsの章をテキトーに訳した

The Java EE 7 Tutorial22 Bean Validation: Advanced Topicsの章をテキトーに訳した。

22 Bean Validation: Advanced Topics

この章では、カスタム制約(custom constraints), カスタムメッセージ(custom validator messages), Bean Validationを使用した制約グループ、の作成方法について説明します。

この章では以下のトピックを扱います。

  • Creating Custom Constraints
  • Customizing Validator Messages
  • Grouping Constraints
  • Using Method Constraints in Type Hierarchies

22.1 Creating Custom Constraints

Bean Validationは開発者がカスタム制約を作成するためのアノテーション・インターフェース・クラスを定義しています。

22.1.1 Using the Built-In Constraints to Make a New Constraint

Bean Validationには、新規の制約を作成したり再利用するために、制約を組み合わせられるビルトイン制約があります。開発者はいくつかのビルトイン制約で構成されたカスタム制約を定義し、単一アノテーションとしてコンポーネント属性に適用することで、制約の定義を簡略化できます。

@Pattern.List({
  @Pattern(regexp = "[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\."
    +"[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*"
    +"@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?")
})
@Constraint(validatedBy = {})
@Documented
@Target({ElementType.METHOD,
    ElementType.FIELD,
    ElementType.ANNOTATION_TYPE,
    ElementType.CONSTRUCTOR,
    ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface Email {

    String message() default "{invalid.email}";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};

    @Target({ElementType.METHOD,
        ElementType.FIELD,
        ElementType.ANNOTATION_TYPE,
        ElementType.CONSTRUCTOR,
        ElementType.PARAMETER})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @interface List {
        Email[] value();
    }
}

このカスタム制約は属性に適用可能です。

...
@Email
protected String email;
...

22.1.2 Removing Ambiguity in Constraint Targets

カスタム制約は、制約ターゲットを指定するためのvalidationAppliesToを要求する戻り値とメソッド引数の両方に適用可能です。

@Constraint(validatedBy=MyConstraintValidator.class)
@Target({ METHOD, FIELD, TYPE, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
@Retention(RUNTIME)
public @interface MyConstraint {
  String message() default "{com.example.constraint.MyConstraint.message}";
  Class<?>[] groups() default {};
  ConstraintTarget validationAppliesTo() default ConstraintTarget.PARAMETERS;
...
}

この制約はメソッド引数にデフォルトでvalidationAppliesToターゲットを設定します。

@MyConstraint(validationAppliesTo=ConstraintTarget.RETURN_TYPE)
public String doSomething(String param1, String param2) { ... }

上の例では、ターゲットはメソッドの戻り値に設定されています。

22.2 Customizing Validator Messages

Bean Validationにはビルトイン制約用にデフォルトメッセージのリソースバンドルが含まれています。このメッセージはカスタマイズと非英語圏ロケール用のローカライズが可能です。

22.2.1 The ValidationMessages Resource Bundle

ValidationMessagesリソースバンドルとロケールバリアントに含まれるデフォルトのメッセージはオーバーライドが可能です。ValidationMessagesリソースバンドルは通常プロパティファイルで、アプリケーションのデフォルトパッケージにValidationMessages.propertiesとして配置します。

22.2.1.1 Localizing Validation Messages

ValidationMessages.propertiesロケールバリアントは、ファイルのベースネームにロケールプレフィクスとアンダースコアを繋げることで、追加されます。たとえば、SpanishロケールバリアントのリソースバンドルはValidationMessages_es.propertiesとなります。

22.3 Grouping Constraints

制約は一つ以上のグループに追加が可能です。制約のサブセットを作成するために制約グループを使用し、特定オブジェクト検証用の制約を作成します。デフォルトでは、すべての制約はDefault制約グループに含まれます。

制約グループはインターフェースで表現します。

public interface Employee {}
public interface Contractor {}

制約グループは他グループを継承可能です。

public interface Manager extends Employee {}

制約を要素に追加する場合、制約のgroups要素にグループインターフェースのクラス名を指定することで制約の属するグループを宣言します。

@NotNull(groups=Employee.class)
Phone workPhone;

複数グループを宣言するには、グループを({と})で囲み、グループのクラス名をカンマで区切ります。

@NotNull(groups={ Employee.class, Contractor.class })
Phone workPhone;

あるグループが他のグループを継承する場合、グループの検証はスーパーグループで宣言されているすべての制約が検証されます。たとえば、workPhoneフィールドの検証はManagerグループが検証されますが、これはEmployeeManagerのスーパーインターフェースなためです。

22.3.1 Customizing Group Validation Order

デフォルトでは、制約グループは順不同で検証されます。とはいえ、他のグループよりもあるグループを先に検証すべき場合があります。たとえば、ある種のクラスでは、基礎データは拡張データよりも先に検証されるべきです。

グループに検証順序を設定するには、インターフェース定義にjavax.validation.GroupSequenceアノテーションを追加し、検証順序を設定します。

@GroupSequence({Default.class, ExpensiveValidationGroup.class})
public interface FullValidationGroup {}

FullValidationGroupを検証する場合、まずDefaultグループが検証されます。そこでの検証がすべてパスしたあと、ExpensiveValidationGroupグループが検証されます。ある制約がDefaultExpensiveValidationGroup両方の一部の場合、制約はDefaultグループの一部として検証され、その後のExpensiveValidationGroupでは検証されません。

22.4 Using Method Constraints in Type Hierarchies

継承階層のオブジェクトに検証制約を付与する場合、サブタイプ化による意図しないエラー防ぐために特別の配慮が必要です。

型指定をする場合、サブタイプはエラーにならずに置き換えが可能です。たとえば、PersonクラスとそのサブクラスEmployeeがあるとすると、Personインスタンスを使用する場所であっても、Employeeインスタンスを使うことが可能な筈です。もしEmployeePersonメソッドをオーバーライドしてメソッド引数に制約を付与している場合、Personオブジェクトでは正しく動作するコードがEmployeeオブジェクトでは例外をスローする可能性があります。

以下のコードはクラス階層内におけるメソッド引数制約の誤った例を示しています。

public class Person {
...
  public void setEmail(String email) { ... }
}
public class Employee extends Person {
...
  @Override
  public void setEmail(@Verified String email) { ... }
}

@Verified制約をEmployee.setEmailに付与することで、Person.setEmailで妥当だった引数はEmployee.setEmailで非妥当になります。これは、サブタイプのメソッド(の引数)における事前条件の強化(strengthening the preconditions)と言います。サブタイプのメソッド呼び出しの事前条件の強化はするべきではありません。

同様に、メソッド呼び出しの戻り値をサブタイプで弱めるべきではありません。以下のコードはクラス階層におけるメソッド戻り値の制約の誤った例を示しています。

public class Person {
...
  @Verified
  public Email getEmail() { ... }
}
public class Employee extends Person {
...
  @Override
  public Email getEmail() { ... }
}

この例では、Employee.getEmailメソッドは戻り値の@Verified制約を削除しています。Person.getEmailでは検証にパスしない戻り値が、Employee.getEmailではパスします。これは、サブタイプ(の戻り値)における事後条件の緩和(weakening the postconditions)と言います。サブタイプのメソッド呼び出しで事後条件の緩和はするべきではありません。

もし型階層がサブタイプのメソッド呼び出しで事前条件の強化や事後条件の緩和をする場合、Bean Validationがjavax.validation.ConstraintDeclarationExceptionをスローします。

パラレルタイプ(parallel types)は、同一のメソッドシグネチャ複数のインターフェースが持ちそれを実装をするクラスです。このクラスは、事前条件の強化を回避するために、インターフェースに適用される制約に注意する必要があります。

public interface PaymentService {
  void processOrder(Order order, double amount);
...
}
public interface CreditCardPaymentService {
  void processOrder(@NotNull Order order, @NotNull double amount);
...
}
public class MyPaymentService implements PaymentService,
        CreditCardPaymentService {

  @Override
  public void processOrder(Order order, double amount) { ... }
...
}

この場合、MyPaymentServiceCreditCardPaymentServiceprocessOrderメソッドの制約を持ちますが、PaymentService.processOrderを呼び出すクライアントコードはその制約の影響を受けません。これはサブタイプにおける事前条件の強化の一例であり、ConstraintDeclarationExceptionがスローされます。

22.4.1 Rules for Using Method Constraints in Type Hierarchies

以下に示すのは、型階層におけるメソッド検証制約の使用方法のルールです。

  • サブタイプで、実装やオーバーライドするメソッドの引数に制約を追加してはならない。
  • 複数のパラレルタイプを宣言するサブタイプで、実装やオーバーライドするメソッドの引数に制約を追加してはならない
  • サブタイプで、実装やオーバーライドするメソッドの戻り値には制約を追加してもよい。

関連リンク