[Spring] MultiKey는 @RequestBody 가 안된다

Choise.o·2024년 7월 27일
0

1. Problem

Spring boot를 사용한 백엔드 프로젝트에 API를 추가하는 작업 중이었다. 모듈간 통신으로 Request Body에 객체를 담아 전달했고 DTO 구조는 다음과 같았다.

import lombok.*;
import org.apache.commons.collections4.keyvalue.MultiKey;
import java.time.LocalDateTime;
import java.util.*;

@Builder
@Getter @Setter
public class ParamDataDto {


    private Map<MultiKey<Integer>, Integer> dataMap;
    
    ...


}

개발을 마무리하고 테스트를 하는데 다음과 같은 에러 가 떨어졌다.

[WARN ] [27,14:47:51.027] [https-jsse-nio-8080-exec-10 AbstractHandlerExceptionResolver.java:208] logException - Resolved [org.springframework.web.HttpMediaTypeNotSupportedException: Content type 'application/json;charset=UTF-8' not supported]
org.springframework.web.reactive.function.client.WebClientResponseException$UnsupportedMediaType: 415 Unsupported Media Type from POST https://{Server ip}:8080/.../send-data
	at org.springframework.web.reactive.function.client.WebClientResponseException.create(WebClientResponseException.java:212)
	Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException: 
Error has been observed at the following site(s):
...

2. 415 Error 해결

로그에서 확인한 415 에러는 client 에서 보내주는 데이터와 server에서 받는 데이터의 contentType이 다를 때 발생할 수 있다고 한다. 이 문제를 해결하기 위해 아래와 같은 작업을 해줬다.

1) Header에 contentType을 application/json; charset=utf-8으로 설정
2) Controller에 contentType 명시(consumes)

@PostMapping(value = "{uri}/data", consumes = MediaType.APPLICATION_JSON_VALUE)
...

그럼에도 해결되지 않았다^^


3. Request Body 타입 변경(DTO ⇒ String)

Dto를 넘기는게 문제인 것 같아 어떻게든 해결해보고자 String으로 Request Body 데이터를 넘겨봤다. Dto를 Gson으로 직렬화해서 넘기고 Controller에서 Gson으로 역직렬화 해줬다.

...!
일단 415 에러는 해결되면서 api가 호출됐다. 원인은 알 수 없지만 일단 이렇게 처리해볼까 했는데 역직렬화한 데이터를 보니 위의 Dto에서 dataMap 데이터가 제대로 넘어오지 않았다.


4. @RequestBody의 동작 원리 : Jackson

지(식)인의 도움을 받아.. 원인을 찾아냈다. 결론부터 말하자면 원인은 MultiKey였다.

@RequestBody의 동작 원리부터 정리해보자면

  • Springboot starter에는 Json과 java 객체의 직렬화/역직렬화를 해주는 Jackson 라이브러리가 포함되어있다.
  • @RequestBody를 사용하면 Http Request Body로 지정한 JSON을 Jackson 라이브러리를 사용하여 변환해준다.

그렇다면 Jackson 라이브러리는 어떻게 동작하는걸까?

  • 아래 출처의 글을 요약해보면 ObjectMapper를 사용하여 역직렬화를 수행하는데
  • ObjectMapper는 기본 생성자로 Dto를 생성한 뒤 Reflection을 통해 필드 정보를 확인, 데이터를 할당하는 방식으로 객체를 생성한다.
    ⇒ 즉, Jackson으로 직렬화 및 역직렬화하기 위해서는 기본 생성자가 필요하다.

그런데 MultiKey는? 기본 생성자가 없다..!

문제 해결

해결 방법은 간단하다. 기본 생성자가 있는 타입의 객체를 사용하면 된다.
만약 위의 예시처럼 MultiKey를 사용하고 싶다면 우선 multiKey.getKeys()를 통해 키 전체를 순회하며
1) String으로 합쳐 하나의 문자열로 만들거나
2) ArrayList에 idx 순서대로 담아주면 된다. (참고로 List는 또한 interface이기 때문에 생성자가 없어서 안된다.)

+++ 별도의 Deserializer를 만들어서 사용하는 방법도 있는데, 작업이 복잡하기 때문에 빠르게 해결하기 위해 위의 방법을 사용했다.

출처

0개의 댓글