티스토리 뷰

Java

DTO 의 Deserialize(역직렬화): 1부

Nickolodeon 2022. 11. 29. 23:48

직렬화 & 역직렬화 Serialization and Deserialization

아래는 스프링 부트 컨트롤러에서 API 를 정의할 때 흔히 볼 수 있는 모습이다:

사용자의 아이디와 비밀번호를 담은 UserLoginRequest DTO 를 받아 서비스에서 사용자에게 토큰을 발급해서 로그인 해준다.

이 API 를 Swagger 를 사용해서 테스트할 때, 다음과 같이 Request body 부분에 JSON 형태의 정보를 넣어준다.

스웨거를 사용하는 모습

이렇게 클라이언트로부터 받은 Request body, JSON 정보를 자바 객체 (여기서는 UserLoginRequest) 로 변환하는 과정을 역직렬화 (Deserialization) 라고 부른다.

 

직렬화 (Serialization) 는 그 반대인 자바 객체를 JSON 형태로 변환하는 과정을 말한다.

 

역직렬화 시 목적지인 DTO 객체를 생성하여 클라이언트로부터 JSON 형태로 넘어온 데이터를 넣어주는데,

원칙 상으로는 이 과정에서 DTO에 @NoArgsConstructor, 즉 빈 생성자가 있어야만 객체를 정상적으로 생성한다.

그러므로 만약 매개변수를 가진 생성자만 있으면 DTO 를 생성하지 못하고 예외가 발생하는 것이 일반적인 경우이다.

하지만, 프로젝트를 하다보면 분명 @NoArgsConstructor 가 붙어있지 않은데도 정상적으로 작동할 때가 있다.

나의 경우도 아래와 같은 UserLoginRequest 으로도 위 컨트롤러에서 작성한 API 가 정상적으로 작동했다.

DTO 엔 @NoArgsConstructor 가 없는데 정상적으로 작동했다.

알아본 결과, 역직렬화 과정에서 Jackson (JSON 라이브러리) 은 ObjectMapper 를 사용해서 JSON 데이터를 read() 메서드를 사용해서 읽고 특정 방식을 이용해 변환한다. 아래 블로그에 이를 알아내는 과정이 상세하게 설명돼 있으니 참고하자.

Object Mapper 를 사용해서 역직렬화한다는 사실을 알아내는 과정

여기서 특정 방식이 무엇인지 간결하게 설명하고자 이 포스트를 작성하게 되었다.

@NoArgsConstructor 가 있을 때 vs 없을 때

먼저, 디폴트 생성자의 유무에 따라 달라지는 곳이 어디인지 알아보자.

 

우선, ObjectMapper 에서 사용하는 read() 메서드에서 시작해 차례대로 호출되는 메서드들은 아래와 같다:

read() 는 readJavaType() 을 호출한다.
readJavaType() 은 readValue() 를 호출한다.
readValue() 는 _readMapAndClose() 를 호출한다.
_readMapAndClose() 는 readRootValue() 를 호출한다.
readRootValue() 에서 deserialize() 가 호출된다. 역직렬화가 이루어지는 부분이다.

이제 deserialize 메서드가 호출된다.

아래의 deserialize() 가 구현된 코드 위의 설명을 보면 bean 객체 (POJO) 를 위한 주요 역직렬화 메서드라고 쓰여있다.

UserLoginRequest 는 빈으로 등록된 POJO 가 맞기 때문에 이 메서드로 역직렬화된다는 것을 이해할 수 있다.

먼저 deserialize 는 다음과 같이 또 차례대로 몇 개의 메서드를 호출한다:

deserialize() 는 deserializeFromObject() 를 호출한다.

자, 이제 디폴트 생성자의 유무에 따라 달라지는 곳을 발견했다. 아래 코드를 보면, deserializeFromObject() 에서는 if (_nonStandardCreation) 에 따라 다른 메서드를 호출한다.
즉 디폴트 생성자가 없는 "non-standard creation" 상황일 때는 deserializeFromObjectUsingNonDefault() 를,
디폴트 생성자가 있는 "standard creation" 상황일 때는 createUsingDefault() 메서드를 호출한다.
메서드들의 이름에서 유추할 수 있듯, 하나는 빈 생성자를 활용하고, 다른 하나는 무언가 다른 방식을 사용한다.

@NoArgsConstructor 가 없이, @AllArgsConstructor 와 @Getter 만 있을 때

빈 생성자가 없을 때 무언가 다른 방식을 사용한다.

 

@NoArgsConstructor 가 있을 때

빈 생성자가 있을 때에는 standard creation 방식으로 역직렬화를 진행한다.

무언가 다른 방식

특정 방식에 대한 궁금증이 사실 이 포스트를 작성하게 된 주요 이유이다.
쓰다 보니 길어져 중요성이 가장 높은 글이 마지막에 오게 되어버렸다.

그래서, 이 내용은 다음 포스트에서 이어서 작성하도록 하겠다.

 

출처

https://velog.io/@conatuseus/RequestBody%EC%97%90-%EC%99%9C-%EA%B8%B0%EB%B3%B8-%EC%83%9D%EC%A0%95%EC%9E%90%EB%8A%94-%ED%95%84%EC%9A%94%ED%95%98%EA%B3%A0-Setter%EB%8A%94-%ED%95%84%EC%9A%94-%EC%97%86%EC%9D%84%EA%B9%8C-2-ejk5siejhh

 

@RequestBody에 왜 기본 생성자는 필요하고, Setter는 필요 없을까? #2

이전 글에서는 어떻게 @RequestBody를 처리하는지를 알아보기 위한 과정을 설명했습니다. 이번 글에서는 @RequestBody를 바인딩하는 ObjectMapper에 대해 알아보고, 결론을 짓겠습니다. 참고로 아래 사진

velog.io

 

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/02   »
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28
글 보관함