본문 바로가기
Spring

[Spring] 스프링에서 직렬화, 역직렬화

by 감자b 2025. 1. 25.

테스트를 작성하는데 HTTP Body 정보가 DTO에 매핑이 되지 않는 문제가 발생하였다.

찾아보니 JSON이 DTO로 매핑되는 과정에서 기본 생성자가 없기 때문에 발생하는 문제였다.

따라서 스프링이 직렬화, 역직렬화를 어떻게 하는지 자세히 알아보려고 한다.

 

- 문제의 코드

@Getter
@Builder
@AllArgsConstructor
public static class InviteMemberDTO {
	@NotBlank
	String email;
}

직렬화, 역직렬화

  • 직렬화
    • 객체를 외부 저장이나 전송 가능한 형태(Byte)로 변환
    • JSON, XML 등으로 변환
    • 자바에선 ObjectMapper.writeValueAsString() 사용하여 변환 가능하다.
  • 역직렬화
    • 외부 데이터(Byte)를 프로그램 내에서 사용 가능한 객체로 변환
    • 저장/전송된 데이터를 객체로 복원

스프링은 직렬화, 역직렬화를 어떻게 처리하는가?

클라이언트에게 요청이 오고 해당 요청 메서드에 @RequestBody가 붙어있다면 HttpMessageConverter가 HTTP 메시지 바디의 내용(JSON)을 객체로 변환시켜준다.

마찬가지로 @ResponseBody가 존재하면 viewResolver 대신에 HttpMessageConverter가 동작하여 해당 타입에 맞는 메시지 컨버터가 처리되어 해당 타입의 내용을 HTTP의 Body에 직접 반환하게 된다.

 

좀 더 자세한 동작 과정은 아래 링크에서 확인할 수 있다.

 

[Spring] Spring MVC

MVC 패턴하나의 서블릿이나, JSP로 처리하던 것을 컨트롤러(Controller)와 뷰(View)라는 영역으로 서로 역할을 나눈 것모델(Model) : 뷰에 출력할 데이터를 담아두는 영역으로 필요한 데이터를 모두 모델

hbb-devlog.tistory.com

 

이게 가능한 이유는 아래의 의존성을 추가하면 Jackson 라이브러리를 함께 추가하기 때문이다.

implementation 'org.springframework.boot:spring-boot-starter-web'

 

스프링에서 타입이 객체 또는 HashMap, 미디어 타입이 application/json 관련 요청, 응답의 경우 MappingJackson2HttpMessageConverter가 동작하는데, 해당 HttpMessageConverter는 Jackson 라이브러리 내부의 objectMapper를 사용하여 객체를 직렬화, 역직렬화 한다.

 

하지만 처음 발생한 문제와 같이 Jackson 라이브러리가 직렬화, 역직렬화 하기 위해선 조건이 필요하다.

Jackson 공식 문서에 아래와 같이 설명되어 있다. (https://jenkov.com/tutorials/java-json/jackson-objectmapper.html)

How Jackson ObjectMapper Matches JSON Fields to Java Fields
By default Jackson maps the fields of a JSON object to fields in a Java object by matching the names of the JSON field to the getter and setter methods in the Java object. Jackson removes the "get" and "set" part of the names of the getter and setter methods, and converts the first character of the remaining name to lowercase.

 

Jackson은 JSON 데이터 필드의 이름을 Java 객체의 getter 및 setter 메서드와 일치시켜 Java 객체의 필드에 매핑한다.

이 때 getter 및 setter 메서드의 이름에서 "get" 및 "set" 부분을 제거하고 나머지 이름의 첫 번째 문자를 소문자로 변환한다.

따라서 Getter가 필수로 있어야 한다.

또한 Jackson과 같은 JSON 라이브러리는 객체를 역직렬화(JSON → 객체)할 때 리플렉션을 사용한다.

이 과정에서 기본 생성자가 없으면 역직렬화 시점에 객체를 생성할 수 없어 예외가 발생한다.

맨 처음 코드에서 테스트의 편의를 위해 @Builder, 전체 생성자를 만들었지만 이로 인해 역직렬화에 필요한 기본 생성자가 없기 때문에 Http Body의 정보가 객체로 변환되지 못한 것 이었다.


추가 내용

1. DTO에 필드가 2개 이상인 경우

DTO에 필드가 2개 이상인 경우에는 기본 생성자가 없어도 역직렬화가 되는 것을 확인하였다.

스프링 부트를 사용하면 jackson-module-parameter-names 모듈을 자동 구성하는데, 이는 DTO의 생성자 파라미터 이름을 JSON 필드와 자동으로 매칭하여 일치하면 JsonCreator.Mode.PROPERTIES 모드로 간주한다.

이로 인해 2개 이상의 필드를 가진 DTO는 기본 생성자 없이도 역직렬화가 가능한데, 단일 필드 DTO의 경우 Jackson이 역직렬화 모드(PROPERTIES 또는 DELEGATING)를 명확히 결정하지 못하기 때문에 기본 생성자가 필요하다고 한다.

 

2. @ModelAttribute의 경우

@ModelAttribute는 쿼리 파라미터에 데이터를 객체 형태로 받고 싶을 때 사용한다.

컨트롤러에서 @ModelAttribute Data data를 인자로 받아올 때 동작 방식은 다음과 같다.

 

- 기본 생성자가 있을 때

1.  Data 인스턴스 생성

2. 요청 파라미터의 이름으로 Data 객체의 프로퍼티를 찾음

3. 해당 프로퍼티의 setter를 호출해서 파라미터의 값을 입력

 

- 기본 생성자가 없을 때

필드에 매칭되는 파라미터를 가진 생성자로 바인딩

 

3. 자바의 리플렉션을 활용하여 JSON 데이터를 객체로 변환할 때 기본 생성자가 필요하다. 이는 객체 생성 후 값 초기화를 위한 setter 등의 사용으로 인해 불변성이 깨질 수도 있는데 리플렉션의 경우 private 접근 제어자에 접근, 값 변경이 가능하므로 @NoArgsConstructor(access = AccessLevel.PRIVATE)로 무분별한 생성을 막도록 하였다.