오봉이와 함께하는 개발 블로그

스프링 MVC 2 - BeanValidation HTTP 메시지 컨버터 본문

BE/Spring

스프링 MVC 2 - BeanValidation HTTP 메시지 컨버터

오봉봉이 2022. 8. 28. 00:19
728x90

Bean Validation - HTTP 메시지 컨버터

@Valid, @ValidatedHttpMessageConverter(@RequestBody)에도 적용할 수 있다.

참고
@ModelAttribute는 HTTP 요청 파라미터(URL 쿼리 스트링, POST Form)를 다룰 때 사용한다.
@RequestBody는 HTTP Body의 데이터를 객체로 변환할 때 사용한다. 주로 API JSON 요청을 다룰 때
사용한다.

ValidationItemApiController 생성

@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의 경우 3가지 경우를 나누어 생각해야 한다.

  • 성공 요청 : 성공
  • 실패 요청 : JSON을 객체로 생성하는 것 자체가 실패함
  • 검증 오류 요청 : JSON을 객체로 생성하는 것은 성공했고, 검증에서 실패함

성공 요청

<!-- 성공 요청 (Postman에서 Body raw JSON을 선택해야 한다.) -->
POST http://localhost:8080/validation/api/items/add
{"itemName":"hello", "price":1000, "quantity": 10}

성공 요청 로그
2022-08-25 01:00:52.434  INFO 25868 --- [nio-8080-exec-1] h.i.w.v.ValidationItemApiController      : API 컨트롤러 호출
2022-08-25 01:00:52.434  INFO 25868 --- [nio-8080-exec-1] h.i.w.v.ValidationItemApiController      : 성공 로직 실행

성공 요청 결과
{
    "itemName": "hello",
    "price": 1000,
    "quantity": 10
}

실패 요청

<!-- 실패 요청 (Postman에서 Body raw JSON을 선택해야 한다.) -->
POST http://localhost:8080/validation/api/items/add
{"itemName":"hello", "price":"A", "quantity": 10}


<!-- 실패 요청 로그 -->
2022-08-25 01:03:09.141  WARN 25868 --- [nio-8080-exec-5] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot deserialize value of type `java.lang.Integer` from String "A": not a valid Integer value; nested exception is com.fasterxml.jackson.databind.exc.InvalidFormatException: Cannot deserialize value of type `java.lang.Integer` from String "A": not a valid Integer value at [Source: (PushbackInputStream); line: 1, column: 30] (through reference chain: hello.itemservice.web.validation.form.ItemSaveForm["price"])]

<!-- 실패 요청 결과 -->
{
    "timestamp": "2022-08-24T16:03:09.150+00:00",
    "status": 400,
    "error": "Bad Request",
    "message": "",
    "path": "/validation/api/items/add"
}

log.info("API 컨트롤러 호출");에 대한 로그가 뜨지 않는다.
이 경우 ItemSaveForm 객체를 만들지 못하기 때문에 컨트롤러 자체가 호출되지 않고 그 전에 예외가 발생하며, Validator도 실행되지 않는다
결과적으로 HttpMessageConverter에서 요청 JSON을 ItemSaveForm객체로 생성하는데 실패했다.

검증 오류 요청

이번에는 HttpMessageConverter는 성공하지만 검증(Validator)에서 오류가 발생하는 경우를 확인해보자.

<!-- 검증 오류 요청 (Postman에서 Body raw JSON을 선택해야 한다.) -->
POST http://localhost:8080/validation/api/items/add
{"itemName":"hello", "price":1000, "quantity": 10000}

<!-- 검증 오류 로그 -->
2022-08-25 01:10:15.339  INFO 25868 --- [nio-8080-exec-7] h.i.w.v.ValidationItemApiController      : 검증 오류 발생 errors=org.springframework.validation.BeanPropertyBindingResult: 1 errors Field error in object 'itemSaveForm' on field 'quantity': rejected value [10000]; 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 이하여야 합니다]

<!-- 검증 오류 결과 -->
[
    {
        "codes": [
            "Max.itemSaveForm.quantity",
            "Max.quantity",
            "Max.java.lang.Integer",
            "Max"
        ],
        "arguments": [
            {
                "codes": [
                    "itemSaveForm.quantity",
                    "quantity"
                ],
                "arguments": null,
                "defaultMessage": "quantity",
                "code": "quantity"
            },
            9999
        ],
        "defaultMessage": "9999 이하여야 합니다",
        "objectName": "itemSaveForm",
        "field": "quantity",
        "rejectedValue": 10000,
        "bindingFailure": false,
        "code": "Max"
    }
]

로그를 보면 검증 오류가 정상 수행된 것을 확인할 수 있다.

return bindingResult.getAllErrors();ObjectErrorFieldError를 반환한다.
스프링이 이 객체를 JSON으로 변환해서 클라이언트에 전달했다.
예시를 위해 검증 오류 객체(검증 오류 결과)를 그대로 반환했다.
실제 개발할 때는 이 객체(검증 오류 결과)들을 그대로 사용하지 않고, 객체 정보 중 필요한 데이터만 뽑아서 별도의 API 스펙을 정의하고, 그에 맞는 객체를 만들어서 검증 오류 결과로 반환해야 한다.

@ModelAttribute vs @RequestBody

HTTP 요청 파리미터를 처리하는 @ModelAttribute는 각각의 필드 단위로 세밀하게 적용된다.
그래서 특정 필드에 타입이 맞지 않는 오류가 발생해도 나머지 필드는 정상 처리할 수 있었다.
HttpMessageConverter@ModelAttribute와 다르게 각각의 필드로 적용되는 것이 아니라, 전체 객체 단위로 적용된다.
따라서 메시지 컨버터의 작동이 성공해서 ItemSaveForm객체를 만들어야 @Valid, @Validated가 적용된다.

  • @ModelAttribute
    • 필드 단위로 정교하게 바인딩이 적용된다.
    • 특정 필드가 바인딩 되지 않아도 나머지 필드는 정상 바인딩 되고, Validator를 사용한 검증도 적용할 수 있다.
  • @RequestBody
    • HttpMessageConverter 단계에서 JSON 데이터를 객체로 변경하지 못하면 이후 단계 자체가 진행되지 않고 예외가 발생한다.
    • 컨트롤러도 호출되지 않고, Validator도 적용할 수 없다.
출처 : 인프런 김영한 지식공유자님 강의 스프링 MVC 2편 - 백엔드 웹 개발 활용 기술
728x90
Comments