Bean Vaildtaion - API 컨트롤러

나무·2023년 11월 19일

스프링 MVC

목록 보기
6/12
post-thumbnail

1. API 통신시엔 검증을 어떻게 할 까?

HTTP 메시지에 대한 검증이 필요할 때도 파라미터 옆에 @Validated 를 붙여주면된다.

package hello.itemservice.web.validation;

import hello.itemservice.web.validation.form.ItemSaveForm;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

@Slf4j
@RestController
@RequestMapping("/validation/api/items")
public class ValidationItemApiController {

    @PostMapping("/add")
    public Object addItem(@RequestBody @Validated ItemSaveForm form, BindingResult bindingResult) {

        log.info("API 컨트롤러 호출");

        if (bindingResult.hasErrors()) {
            log.info("검증 오류 발생 errors={}", bindingResult);
            return bindingResult.getAllErrors();
        }

        log.info("성공 로직 실행");
        return form;
    }
}

※ API 통신의 경우 @ModelAttribute 가 아닌 @RequestBody 를 사용한다.

※ 컨트롤러가 호출될때마다 로그가 출력되도록 하였다.

POSTMAN 을 이용해서 HTTP Request 메시지를 서버로 전송해보자.

선택된 항목들을 잘 체크해주고 SEND 로 전송을하면 아래와 같이 Body 에 정상적으로 JSON 타입의 데이터가 담겨서 날아간것을 확인할 수 있다.

또한 컨트롤러도 정상적으로 호출이 된다.

그렇다면 이제 한번 검증에 실패하도록 데이터를 입력해보자!

2. API 컨트롤러와 검증

1) 타입 에러

Integer 타입의 price문자열을 담아서 전송하면 당연히 타입 에러가 발생 할 것이다.

과연 서버에는 어떻게 출력이 될까?

Controller 출력

2023-11-19 15:45:59.358  WARN 9752 --- [nio-8080-exec-8] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot deserialize value of type `java.lang.Integer` from String "QQQ": not a valid Integer value; nested exception is com.fasterxml.jackson.databind.exc.InvalidFormatException: Cannot deserialize value of type `java.lang.Integer` from String "QQQ": not a valid Integer value
 at [Source: (PushbackInputStream); line: 1, column: 30] (through reference chain: hello.itemservice.web.validation.form.ItemSaveForm["price"])]

위와 같이 Exception 이 터지고 컨트롤러는 호출이 되지 않는다.

타입 에러 의 경우 애초에 JSON 타입의 데이터를 만드는데 실패 했기 때문에 @RequestBody 가 데이터를 읽어들이지 못하고 전달된 데이터들을 ItemSaveForm 에 담는데 실패하였다

그렇기때문에 @Validated 검증기 도 돌지 못하고 그냥 바로 예외가 터진 것이다.

즉, 객체 생성에 실패 한 셈이므로 예외가 터져버린다.

그럼 API 통신의 경우 타입 에러 를 처리하지 못하는것인가?

다행히 스프링에서는 예외처리에 대한 방식이 또 따로 존재하기 때문에 걱정할 필요가 없다. 하지만 이번 포스트에서는 다루지 않는다. (포스트 올리기전까지 기둘,,)

2) 필드 에러

이번엔 수량(quantity)을 조건 범위 보다 크게 입력해서 전송을 해보자.

이번엔 @Validated 가 잘 동작했는지 기대했던 메시지 코드와 메시지 내용들이 잘 반환된것을 확인 할 수 있다.

Controller 출력

2023-11-19 15:58:54.770  INFO 9752 --- [nio-8080-exec-1] h.i.w.v.ValidationItemApiController      : 검증 오류 발생 errors=org.springframework.validation.BeanPropertyBindingResult: 1 errors
Field error in object 'itemSaveForm' on field 'quantity': rejected value [99999]; codes [Max.itemSaveForm.quantity,Max.quantity,Max.java.lang.Integer,Max]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [itemSaveForm.quantity,quantity]; arguments []; default message [quantity],9999]; default message [9999 이하여야 합니다]

컨트롤러도 정상적으로 호출 되는 것을 확인 할 수 있다.

※ [주의]
현재는 메시지코드나 필드명, 객체명들이 전부 출력되지만 실무에서는 가릴건 가리고 클라이언트가 필요한 내용만 출력되게 해야한다.

3. 왜 API 통신만 타입에러를 못잡아?

아니 form 데이터의 경우 타입에러도 알아서 BindingResult 가 알아서 잘 잡아줬던것 같은데 왜 API 통신에서는 그게 안될까?

이는 @ModelAttribute@RequestBody 의 데이터 바인딩 방식이 달라서 그런데

@ModelAttribute 의 경우 필드 단위 까지 세밀하게 적용되기 때문에 특정 필드의 타입에러가 발생한다 할지라도 나머지 필드는 정상적으로 처리가 가능했다.

하지만 @RequestBody 의 경우 바인딩 되기전에 먼저 JSON 타입의 HTTP 메시지를 Item 객체로 변환해주는 HTTPMessageConverter 가 중간에 껴있다.

이때 이 컨버터가 애초에 잘못된 JSON 데이터를 전달 받게 되면 객체로 변경 자체를 못하기 때문에 다음 단계로 진행을 실패하게 되는것이다.

본 포스트는
김영한의 스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 강의 를 보고 정리했습니다.

profile
🍀 개발을 통해 지속 가능한 미래를 만드는데 기여하고 싶습니다 🍀

0개의 댓글