Bean Validation 2.0を使ったコンテナ要素の検証

Bean Validation 2.0を使用したコンテナ要素の検証

1. 概要

Java Bean Validation仕様の2.0バージョンでは、コンテナの要素を検証する可能性など、いくつかの新機能が追加されています。

この新しい機能は、Java 8で導入された型注釈を利用しています。 したがって、動作するにはJavaバージョン8以降が必要です。

検証アノテーションは、コレクション、Optionalオブジェクト、その他の組み込みコンテナーやカスタムコンテナーなどのコンテナーに追加できます。

Java Bean Validationの概要と、必要なMavenの依存関係を設定する方法については、previous article hereを確認してください。

次のセクションでは、各タイプのコンテナの要素の検証に焦点を当てます。

2. コレクション要素

タイプjava.util.Iterablejava.util.List、およびjava.util.Mapのコレクションの要素に検証アノテーションを追加できます。

リストの要素を検証する例を見てみましょう。

public class Customer {
     List<@NotBlank(message="Address must not be blank") String> addresses;

    // standard getters, setters
}

上記の例では、Customerクラスのaddressesプロパティを定義しました。これには、空のStringsにすることのできない要素が含まれています。

the @NotBlank validation applies to the String elements, and not the entire collectionに注意してください。 コレクションが空の場合、検証は適用されません。

空のStringaddressesリストに追加しようとすると、検証フレームワークがConstraintViolationを返すことを確認しましょう。

@Test
public void whenEmptyAddress_thenValidationFails() {
    Customer customer = new Customer();
    customer.setName("John");

    customer.setAddresses(Collections.singletonList(" "));
    Set> violations =
      validator.validate(customer);

    assertEquals(1, violations.size());
    assertEquals("Address must not be blank",
      violations.iterator().next().getMessage());
}

次に、タイプMapのコレクションの要素を検証する方法を見てみましょう。

public class CustomerMap {

    private Map<@Email String, @NotNull Customer> customers;

    // standard getters, setters
}

we can add validation annotations for both the key and the value of a Map elementに注意してください。

無効なメールを含むエントリを追加すると、検証エラーが発生することを確認しましょう。

@Test
public void whenInvalidEmail_thenValidationFails() {
    CustomerMap map = new CustomerMap();
    map.setCustomers(Collections.singletonMap("john", new Customer()));
    Set> violations
      = validator.validate(map);

    assertEquals(1, violations.size());
    assertEquals(
      "Must be a valid email",
      violations.iterator().next().getMessage());
}

3. Optional

検証制約は、Optional値にも適用できます。

private Integer age;

public Optional<@Min(18) Integer> getAge() {
    return Optional.ofNullable(age);
}

年齢が低すぎるCustomerを作成し、これが検証エラーになることを確認しましょう。

@Test
public void whenAgeTooLow_thenValidationFails() {
    Customer customer = new Customer();
    customer.setName("John");
    customer.setAge(15);
    Set> violations
      = validator.validate(customer);

    assertEquals(1, violations.size());
}

一方、ageがnullの場合、Optional値は検証されません。

@Test
public void whenAgeNull_thenValidationSucceeds() {
    Customer customer = new Customer();
    customer.setName("John");
    Set> violations
      = validator.validate(customer);

    assertEquals(0, violations.size());
}

4. 非ジェネリックコンテナ要素

型引数に注釈を追加するだけでなく、@UnwrapByDefault注釈が付いた型の値エクストラクターがある限り、非ジェネリックコンテナーに検証を適用することもできます。

値抽出は、検証のためにコンテナから値を抽出するクラスです。

The reference implementation contains value extractors for OptionalInt, OptionalLong and OptionalDouble

@Min(1)
private OptionalInt numberOfOrders;

この場合、@Minアノテーションは、コンテナではなく、ラップされたInteger値に適用されます。

5. カスタムコンテナ要素

組み込みの値抽出機能に加えて、独自の値抽出機能を定義し、コンテナタイプに登録することもできます。

このようにして、カスタムコンテナの要素に検証アノテーションを追加できます。

companyNameプロパティを含む新しいProfileクラスを追加しましょう。

public class Profile {
    private String companyName;

    // standard getters, setters
}

次に、Customerクラスに@NotBlankアノテーションを付けてProfileプロパティを追加します。これにより、companyNameが空のStringではないことが確認されます。

@NotBlank
private Profile profile;

これを機能させるには、プロファイルオブジェクトではなく、companyNameプロパティに適用する検証を決定する値エクストラクタが必要です。

ValueExtractorインターフェースを実装し、extractValue()メソッドをオーバーライドするProfileValueExtractorクラスを追加しましょう。

@UnwrapByDefault
public class ProfileValueExtractor
  implements ValueExtractor<@ExtractedValue(type = String.class) Profile> {

    @Override
    public void extractValues(Profile originalValue,
      ValueExtractor.ValueReceiver receiver) {
        receiver.value(null, originalValue.getCompanyName());
    }
}

このクラスは、@ExtractedValueアノテーションを使用して抽出された値のタイプも指定する必要があります。

また、the @UnwrapByDefault annotation that specifies the validation should be applied to the unwrapped value and not the containerを追加しました。

最後に、ProfileValueExtractorクラスのフルネームを含むMETA-INF/servicesディレクトリにjavax.validation.valueextraction.ValueExtractorというファイルを追加してクラスを登録する必要があります。

org.example.javaxval.container.validation.valueextractors.ProfileValueExtractor

ここで、Customerオブジェクトをprofileプロパティで空のcompanyNameで検証すると、検証エラーが表示されます。

@Test
public void whenProfileCompanyNameBlank_thenValidationFails() {
    Customer customer = new Customer();
    customer.setName("John");
    Profile profile = new Profile();
    profile.setCompanyName(" ");
    customer.setProfile(profile);
    Set> violations
     = validator.validate(customer);

    assertEquals(1, violations.size());
}

hibernate-validator-annotation-processorを使用している場合、@UnwrapByDefaultとしてマークされているときに、カスタムコンテナクラスに検証アノテーションを追加すると、バージョン6.0.2でコンパイルエラーが発生することに注意してください。

これはknown issueであり、将来のバージョンで解決される可能性があります。

6. 結論

この記事では、Java Bean Validation 2.0.を使用していくつかのタイプのコンテナ要素を検証する方法を示しました。

over on GitHubの完全なソースコードを見つけることができます。