RequestBody를 원리를 이해하기 전, 먼저 개념 정리를 해보자.
직렬화와 역직렬화
직렬화
는 오브젝트를 문자열 또는 바이트 스트림으로 변환하는 것이다. 보통 오브젝트를 데이터베이스, 파일, 캐시 등 저장하기 위한 연속적인 데이터로 변환할 때 사용된다.
이와 반대로 역직렬화
는 문자열 또는 바이트 스트림을 오브젝트로 변환하는 것을 의미한다.
쉽게 말해 직렬화 종류는 우리가 자주 사용하는 JSON, XML 등이 존재한다.
이런 직렬화와 역직렬화 시 사용되는 것이 Http Message Converter이다.
Http Message Converter 는 HTTP Request Body 를 Java 오브젝트로 변환( 역직렬화 )하거나 Java 오브젝트를 특정 형태로 변환( 직렬화 )하는 역할을 수행한다.
RequestBody는 언제 처리될까?
크게 보았을 때, DispatcherServlet이 받은 요청은 RequestMappingHandlerAdpater에 의해 적절한 Argument Resolver에 의해 처리되도록 위임시킨다.
RequestMappingHandlerAdapter
DispatcherServlet은 요청이 들어오면 직접적으로 처리하지 않고 HandlerAdapter에게 위임시킨다.
이 중 RequestMappingHandlerAdapter는 @RequestMapping을 처리를 하는 HandlerAdapter이다.
Spring 3.1부터 프로세스의 요청을 처리할 필요가 있느 메서드를 잘 판단한다.
Argument Resolver
컨트롤러 메서드의 파라미터 중 특정 조건에 맞는 파라미터가 있다면, 요청에 들어온 값을 이용해 원하는 객체를 만들어 바인딩한다.
이 중 우리가 눈여겨볼 @RequestBody도 존재한다.
아까 전에 살펴본 HttpMessageConverter도 ArgumentResolver와 비슷한 역할을 한다고 알았다. 그러면 HttpMessageConverter는 정확히는 무슨 역할을 하는 것일까?
HttpMessageConverter
HttpMessageConverter는 request의 body를 Java Object로 컨버팅하는 역할을 수행한다.
Spring boot는 다양한 메시지 컨버터를 제공하는데, 대상 클래스 타입과 미디업 타입을 체크해서 사용 여부를 결정한다.
HTTP Message Converter는 Argument Resolver가 사용하는 것으로, returnValueHandler에서 반환할 때도 사용한다.
이 중 JSON을 POJO로 변경해주는 메시지 컨버터가 MappingJackson2HttpMessageConverter
이다.
Implementation of HttpMessageConverter that can read and write JSON using Jackson 2.x's ObjectMapper.
Spring 공식 문서에서 말해주듯이 MappingJackson2HttpMessageConverter
는 Jackson ObjectMapper를 이용하는 HttpMessageConverter의 구현체이다.
요약하면, 스프링 내부의 Jackson 라이브러리라는 ObjectMapper가 JSON을 POJO로 역직렬화 해준다.
그러면 어떻게 Jackson ObjectMapper가 JSON 필드와 Java 필드를 매치 시킬까?
getter 또는 setter 메서드의 'get', 'set' 부분을 떼어내고 남은 단어 중 첫 글자를 소문자로 바꾼 단어와 body에 들어온 key와 같을때 매핑됨.
예를 들어, JSON에 brand: 1
는 Java의 getBrand()
와 setBrand()
와 매치된다.
기본생성자의 필요성
그런데 이번 미션을 진행하면서 발생한게 기본생성자의 부재의 따른 JSON parse 에러였다.
먼저 이를 이해하기 전 java의 Reflection을 알아보자
리플렉션은 구체적인 클래스 타입을 알지 못하더라도 그 클래스의 메서드, 타입, 변수들에 접근할 수 있도록 해주는 자바 API를 말하며, 컴파일 시간이 아닌 실행 시간에 동적으로 특정 클래스의 정보를 추출할 수 있는 프로그래밍 기법이라 할 수 있다.
출처: https://jeongkyun-it.tistory.com/225
동적으로 클래스를 사용해야할 때 사용하기 때문에 프레임워크나 IDE에서 Reflection 동적 바인딩을 이용한 기능을 제공한다.
마찬가지로 @RequestBody 또한 Reflection API를 이용하여 동적으로 클래스를 사용한다.
정확히는 Jackson의 Object Mapper가 @RequestBody의 매핑을 진행한다.
ObjectMapper Reflaction
기능을 이용하여, 객체의 생성자, 게터, 세터를 이용한다.
그러나 java의 Reflection API는 생성자의 파라미터를 가져올 수 없다!
따라서, 파라미터가 있는 생성자는 이용할 수 없어 객체를 생성하기 위해선 기본 생성자가 무조건 필요한 것이다.
그 때문에, @RequestBody의 Reservation의 기본 생성자가 없는 경우, 당연히 에러가 발생하게 된다.
오류가 발생한 코드를 보자.
List<Reservation> reservations = RestAssured.given().log().all()
.when().get("/reservations")
.then().log().all()
.statusCode(200).extract()
.jsonPath().getList(".", Reservation.class);
해당 테스트 코드에서 오류가 발생한 getList()
를 더 유심히 볼 필요가 있다.
getList() 내부에선 jsonStringObject()를 이용하여 매핑하는 것 같으로 예측된다.
예상처럼 해당 코드에서 역직렬화가 발생하는 것을 볼 수 있다.
이 때문에 Reservation의 기본 생성자가 없는 내 코드는 테스트 코드 실행중 오류가 발생한 것이다.
참고 자료
[Spring] @RequestBody의 동작 원리는 무엇일까?
@ReqestBody를 사용하는 방법은 알겠다. 문득, 어떻게 DTO와 요청 JSON을 매핑하는지 동작 원리가 궁금했다. 어떻게 @RequestBody는 객체와 JSON을 매핑할까? 조사해본 내용들을 정리 해보려고 한다. ✔️ @
hsuzzang.tistory.com
https://gist.github.com/taekwon-dev/666eefdddda2207b86368dcc5ab12a4b
[Spring MVC] @RequestBody 동작 원리 [1] - Http Message Converter.md
GitHub Gist: instantly share code, notes, and snippets.
gist.github.com
Jackson ObjectMapper
The <em>Jackson ObjectMapper</em> can read JSON into Java objects and write Java objects to JSON. This Jackson ObjectMapper tutorial explains how to use the Jackson ObjectMapper class.
jenkov.com
https://velog.io/@pp8817/HTTP-Message-Converter%EC%99%80-ArgumentResolver
✏️ HTTP Message Converter와 ArgumentResolver
뷰 템플릿으로 HTML을 생성해서 응답하는 것이 아니라, HTTP API처럼 JSON 데이터를 HTTP 메시지 바디에서 직접 읽거나 쓰는 경우 HTTP 메시지 컨버터를 사용하면 편리하다.
velog.io
https://traveloving2030.github.io/jiwon/ArgumentResolver/
Argument Resolver
web
traveloving2030.github.io
https://da-nyee.github.io/posts/woowacourse-why-the-default-constructor-is-needed/
[우아한테크코스] 기본 생성자가 필요한 이유 (Why the default constructor is needed) (feat. Jackson ObjectMapper
개요
da-nyee.github.io
https://www.baeldung.com/spring-mvc-handler-adapters
'🛠 백엔드 > Spring' 카테고리의 다른 글
북마크 구현에 관한 고민 (1) | 2025.03.13 |
---|---|
[Spring] 민감한 정보 관리하기 wt. secretKey (0) | 2024.07.01 |
JDBC란 무엇인가? (+ Spring JDBC) (0) | 2024.05.19 |
[초록스터디] Spring MVC 회고 (0) | 2024.05.15 |
[Java] 일급 컬렉션을 왜 사용할까? (0) | 2024.05.05 |