web-dev-qa-db-ja.com

検証が失敗したときにSpring RestControllerでスローする例外の種類は何ですか?

SpringではRestControllerに対応するメソッドパラメータに@Validまたは@Validatedと注釈を付けるだけでRequestBodyの入力検証を行います。他の一部の検証は、受信データのいくつかの処理後にのみ実行できます。私の質問は、@Validアノテーションによってスローされる例外に似ているように、どのタイプの例外を使用する必要があるか、および検証結果からこの例外をどのように構成するかです。次に例を示します。

@RequestMapping(method = RequestMethod.POST)
public ResponseEntity<?> createOrder(@RequestBody @Validated(InputChecks.class) Order order) {
    // Some processing of the Order goes here
    Set<ConstraintViolation<Order>> violations = validator.validate(order, FinalChecks.class);
    // What to do now with the validation errors?
    orders.put(order);
    HttpHeaders headers = new HttpHeaders();
    headers.setLocation(ServletUriComponentsBuilder.fromCurrentRequest().path("/" + order.getId()).build().toUri());
    return new ResponseEntity<>(null, headers, HttpStatus.CREATED);
}
13
Gregor

私にとって最も簡単な方法は、エラーオブジェクトでオブジェクトを検証し、それをMethodArgumentNotValidExceptionで使用するように見えます。

@RequestMapping(method = RequestMethod.POST)
public ResponseEntity<?> createOrder(@RequestBody @Validated(InputChecks.class) Order order)
                throws NoSuchMethodException, SecurityException, MethodArgumentNotValidException {
    // Some processing of the Order goes here
    SpringValidatorAdapter v = new SpringValidatorAdapter(validator);
    BeanPropertyBindingResult errors = new BeanPropertyBindingResult(order, "order");
    v.validate(order, errors, FinalChecks.class);
    if (errors.hasErrors()) {
        throw new MethodArgumentNotValidException(
                new MethodParameter(this.getClass().getDeclaredMethod("createOrder", Order.class), 0),
                errors);
    }
    orders.put(order);
    HttpHeaders headers = new HttpHeaders();
    headers.setLocation(ServletUriComponentsBuilder.fromCurrentRequest().path("/" + order.getId()).build().toUri());
    return new ResponseEntity<>(null, headers, HttpStatus.CREATED);
}

このように、2番目の検証ステップ中に検出されたエラーは、@ validatedパラメーターの入力検証中に検出されたエラーとまったく同じ構造を持っています。

16
Gregor

2回目の実行で検証エラーを処理するために、3つの異なるアプローチを考えることができます。最初に、検証エラーメッセージをSetsのConstraintViolationから抽出し、検証エラーメッセージを応答本文として、適切なHTTP応答、たとえば400 Bad Requestを返すことができます。

Set<ConstraintViolation<Order>> violations = validator.validate(order, FinalChecks.class);
if (!violations.isEmpty()) {
    Set<String> validationMessages = violations
                                     .stream()
                                     .map(ConstraintViolation::getMessage)
                                     .collect(Collectors.toSet());

    return ResponseEntity.badRequest().body(validationMessages);
}
// the happy path

このアプローチは、いくつかのコントローラーで二重検証が必要な場合に適しています。それ以外の場合は、まったく新しいExceptionをスローするか、Spring関連の例外を再利用して、MethodArgumentNotValidExceptionと言い、それらを普遍的に処理するControllerAdviceを定義することをお勧めします。

Set<ConstraintViolation<Order>> violations = validator.validate(order, FinalChecks.class);
if (!violations.isEmpty()) {
    throw new ValidationException(violations);
}

そしてコントローラーのアドバイス:

@ControllerAdvice
public class ValidationControllerAdvice {
    @ExceptionHandler(ValidationException.class)
    public ResponseEntity handleValidtionErrors(ValidationException ex) {
        return ResponseEntity.badRequest().body(ex.getViolations().stream()...);
    }
}

MethodArgumentNotValidExceptionのような春の例外の1つをスローすることもできます。そのためには、SetsのConstraintViolationBindingResultのインスタンスに変換し、それをMethodArgumentNotValidExceptionのコンストラクターに渡す必要があります。

7
Ali Dehghani