오봉이와 함께하는 개발 블로그
스프링 MVC 2 - BeanValidation HTTP 메시지 컨버터 본문
Bean Validation - HTTP 메시지 컨버터
@Valid
, @Validated
는 HttpMessageConverter
(@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();
는 ObjectError
와 FieldError
를 반환한다.
스프링이 이 객체를 JSON으로 변환해서 클라이언트에 전달했다.
예시를 위해 검증 오류 객체(검증 오류 결과)를 그대로 반환했다.
실제 개발할 때는 이 객체(검증 오류 결과)들을 그대로 사용하지 않고, 객체 정보 중 필요한 데이터만 뽑아서 별도의 API 스펙을 정의하고, 그에 맞는 객체를 만들어서 검증 오류 결과로 반환해야 한다.
@ModelAttribute vs @RequestBody
HTTP 요청 파리미터를 처리하는 @ModelAttribute
는 각각의 필드 단위로 세밀하게 적용된다.
그래서 특정 필드에 타입이 맞지 않는 오류가 발생해도 나머지 필드는 정상 처리할 수 있었다.HttpMessageConverter
는 @ModelAttribute
와 다르게 각각의 필드로 적용되는 것이 아니라, 전체 객체 단위로 적용된다.
따라서 메시지 컨버터의 작동이 성공해서 ItemSaveForm
객체를 만들어야 @Valid
, @Validated
가 적용된다.
- @ModelAttribute
- 필드 단위로 정교하게 바인딩이 적용된다.
- 특정 필드가 바인딩 되지 않아도 나머지 필드는 정상 바인딩 되고, Validator를 사용한 검증도 적용할 수 있다.
- @RequestBody
- HttpMessageConverter 단계에서 JSON 데이터를 객체로 변경하지 못하면 이후 단계 자체가 진행되지 않고 예외가 발생한다.
- 컨트롤러도 호출되지 않고, Validator도 적용할 수 없다.
출처 : 인프런 김영한 지식공유자님 강의 스프링 MVC 2편 - 백엔드 웹 개발 활용 기술
'BE > Spring' 카테고리의 다른 글
스프링 MVC 2 - 회원 가입 (0) | 2022.08.28 |
---|---|
스프링 MVC 2 - 요구사항, 프로젝트 구조 (0) | 2022.08.28 |
스프링 MVC 2 - Form 전송 객체 분리 소개와 개발 (0) | 2022.08.25 |
스프링 MVC 2 - BeanValidation 한계, groups (0) | 2022.08.24 |
스프링 MVC 2 - Bean Validation 에러 코드, 오브젝트 오류 (0) | 2022.08.24 |