1. 序章

ユーザー入力の検証は、どのアプリケーションでも一般的な要件です。 このチュートリアルでは、オブジェクトのリストをSpringコントローラーへのパラメーターとして検証する方法について説明します。

コントローラレイヤーに検証を追加して、ユーザー指定のデータが指定された条件を満たすことを確認します。

2. Beanへの制約の追加

この例では、映画のデータベースを管理する単純なSpringコントローラーを使用します。 映画のリストを受け入れ、リストで検証を実行した後、それらをデータベースに追加する方法に焦点を当てます。

それでは、javax検証を使用してムービーbeanに制約を追加することから始めましょう。

public class Movie {

    private String id;

    @NotEmpty(message = "Movie name cannot be empty.")
    private String name;

    // standard setters and getters
}

3. コントローラに検証アノテーションを追加する

コントローラを見てみましょう。 まず、@Validatedアノテーションをコントローラークラスに追加します。

@Validated
@RestController
@RequestMapping("/movies")
public class MovieController {

    @Autowired
    private MovieService movieService;

    //...
}

次に、渡されたMovieオブジェクトのリストを検証するコントローラーメソッドを記述しましょう。

@NotEmptyアノテーションを映画のリストに追加して、リストに少なくとも1つの要素が含まれている必要があることを検証します。 同時に、 @Valid アノテーションを追加して、Movieオブジェクト自体が有効であることを確認します。

@PostMapping
public void addAll(
  @RequestBody 
  @NotEmpty(message = "Input movie list cannot be empty.")
  List<@Valid Movie> movies) {
    movieService.addAll(movies);
}

空のMovieリスト入力を使用してコントローラーメソッドを呼び出すと、 @NotEmpty アノテーションが原因で検証が失敗し、次のメッセージが表示されます。

Input movie list cannot be empty.

@Valid アノテーションは、Movieクラスで指定された制約がリスト内のオブジェクトごとに評価されることを確認します。 したがって、リストに空の名前の Movie を渡すと、検証は失敗し、次のメッセージが表示されます。

Movie name cannot be empty.

4. カスタムバリデーター

カスタム制約バリデーターを入力リストに追加することもできます。

この例では、カスタム制約は、入力リストのサイズが最大4つの要素に制限されているという条件を検証します。 このカスタム制約アノテーションを作成しましょう:

@Constraint(validatedBy = MaxSizeConstraintValidator.class)
@Retention(RetentionPolicy.RUNTIME)
public @interface MaxSizeConstraint {
    String message() default "The input list cannot contain more than 4 movies.";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

次に、上記の制約を適用するバリデーターを作成します。

public class MaxSizeConstraintValidator implements ConstraintValidator<MaxSizeConstraint, List<Movie>> {
    @Override
    public boolean isValid(List<Movie> values, ConstraintValidatorContext context) {
        return values.size() <= 4;
    }
}

最後に、@MaxSizeConstraintアノテーションをコントローラーメソッドに追加します。

@PostMapping
public void addAll(
  @RequestBody
  @NotEmpty(message = "Input movie list cannot be empty.")
  @MaxSizeConstraint
  List<@Valid Movie> movies) {
    movieService.addAll(movies);
}

ここで、@MaxSizeConstraintは入力のサイズを検証します。 したがって、入力リストに4つを超える Movie オブジェクトを渡すと、検証は失敗します。

5. 例外の処理

いずれかの検証が失敗すると、ConstraintViolationExceptionがスローされます。 次に、例外処理コンポーネントを追加して、この例外をキャッチする方法を見てみましょう。

@ExceptionHandler(ConstraintViolationException.class)
public ResponseEntity handle(ConstraintViolationException constraintViolationException) {
    Set<ConstraintViolation<?>> violations = constraintViolationException.getConstraintViolations();
    String errorMessage = "";
    if (!violations.isEmpty()) {
        StringBuilder builder = new StringBuilder();
        violations.forEach(violation -> builder.append(" " + violation.getMessage()));
        errorMessage = builder.toString();
    } else {
        errorMessage = "ConstraintViolationException occured.";
    }
    return new ResponseEntity<>(errorMessage, HttpStatus.BAD_REQUEST);
 }

6. APIのテスト

次に、有効な入力と無効な入力を使用してコントローラーをテストします。

まず、APIに有効な入力を提供しましょう。

curl -v -d [{"name":"Movie1"}] -H "Content-Type: application/json" -X POST http://localhost:8080/movies

このシナリオでは、HTTPステータス200の応答を受け取ります。

...
HTTP/1.1 200
...

次に、無効な入力を渡したときのAPI応答を確認します。

空のリストを試してみましょう:

curl -d [] -H "Content-Type: application/json" -X POST http://localhost:8080/movies

このシナリオでは、HTTPステータス400の応答を受け取ります。 これは、入力が@NotEmpty制約を満たさないためです。

Input movie list cannot be empty.

次に、リスト内の5つのMovieオブジェクトを渡してみましょう。

curl -d [{"name":"Movie1"},{"name":"Movie2"},{"name":"Movie3"},{"name":"Movie4"},{"name":"Movie5"}] 
  -H "Content-Type: application/json" -X POST http://localhost:8080/movies

@MaxSizeConstraint 制約に失敗するため、これもHTTPステータス400応答になります。

The input list cannot contain more than 4 movies.

7. 結論

この簡単な記事では、Springでオブジェクトのリストを検証する方法を学びました。

いつものように、例の完全なソースコードはGitHubにあります。