티스토리 뷰
클라이언트에서 전달받은 날짜 포맷의 데이터를 @JsonFormat을 이용하여 ZonedDateTime 타입의 변수에 바인딩할 때주의할 점에 대해 알아보겠습니다.
일단 클라이언트에서 전달받은 날짜 포맷의 데이터를 ZoneDateTime 타입으로 받을 때 UTC로 변환되는 이슈가 있었는데요. 예제코드 (1)을 통해 함께 알아보겠습니다.
예제코드 (1)
ZonedDateTime 타입의 필드를 가진 Product 클래스가 있고 이 필드 위에는 다음과 같은 어노테이션이 선언돼있습니다.
위 예제에서의 @JsonFormat 역할은 "yyyy-MM-dd HH:mm:ss" 패턴의 String 데이터를 ZonedDateTime 타입으로 역직렬화를 해주는 코드이며 Timezone은 Asia/Seoul로 세팅하는 코드입니다.
그리고 해당 데이터를 출력하는 코드입니다.
1 | curl -X POST "http://localhost:8080" -H "Content-Type:application/json" -d "{\"startDate\":\"2023-04-01 00:00:00\"}" | cs |
이제 위 요청정보로 요청해보겠습니다.
2023-04-01 00:00:00으로 데이터를 전송했지만 Timezone이 UTC로 변경된 걸 확인하실 수 있습니다.
이제 왜 UTC로 변환이 되어졌는지 확인해보겠습니다.
원인분석
이 상수는 com.fasterxml.jackson.databind.DeserializationFeature Enum에 정의돼있는 상수입니다.
DeserializationFeature은 직렬화된 데이터를 Java 객체로 역직렬화 해주는 역할을 합니다.
그리고 위와 같은 상수가 정의되어 있습니다.
간단히 설명해 드리자면 이 옵션이 활성화 되었을 경우 MessageConverter에 등록된 Timezone 정보를 기반으로
날짜 데이터를 세팅합니다.
정확히는 MessageConverter는 내부적으로 ObjectMapper를 이용해서 직렬화 / 역직렬화를 진행하는데
ObjectMapper에 Timezone 정보가 들어가 있습니다.
역직렬화는 하는 과정 중에 com.fasterxml.jackson.datatype.jsr310.deser.InstantDeserializer 클래스의 _fromString 메서드를 호출합니다. 이때 value 변수의 값을 확인해보면 우리가 원하는 값이 들어가 있는 걸 확인하실 수 있습니다.
근데 여기서 shouldAdjustToContextTimezone 메서드의 반환 값이 true일 경우 value를 반환하지 않고 다른 값을 반환하는데요. 내부 로직이 어떻게 돼 있는지 확인해보겠습니다.
_adjustToContextTZOverride 변수가 null인지 아닌지에 따라 반환 값이 다른데요. 지금은 null이 저장돼있으니 DeserializationFeature Enum의 ADJUST_DATES_TO_CONTEXT_TIME_ZONE값이 활성화 돼있는지 여부를 반환합니다. 기본값은 true이므로 true가 반환됩니다.
shouldAdjustToContextTimezone 메서드의 반환 값이 true이면 내부로직에서 위 메서드를 실행합니다.
ZoneId를 조회해오기 위함인데요. 더 살펴보겠습니다.
_timezone 변수가 null이 들어가 있으니 DEFAULT_TIMEZONE이 반환됩니다.
DEFAULT_TIMEZONE은 UTC로 지정돼있습니다.
즉, UTC로 변환되는 원인은 ObjectMapper에 Timezone이 설정되지 않았거나 혹은 ADJUST_DATES_TO_CONTEXT_TIME_ZONE 값이 true이기 때문에 발생하였습니다.
이제 문제를 해결해보도록 하겠습니다.
문제해결
위 상수의 기본값을 false로 변경해보겠습니다.
이번에는 해당 if 블록 내부의 코드가 실행되지 않고 그냥 value를 반환합니다.
원하는 값이 출력되었습니다.
이제 ObjectMapper의 Timezone을 Asia/Seoul로 지정해보겠습니다.
아까는 Timezone을 지정하지 않아서 DEFAULT_TIMEZONE이 반환됐지만 지금은 지정된 Timezone이 반환됐습니다.
원하는 값이 출력되었습니다.
예제코드 (2)
위 예제코드 (1)에서 @JsonFormat의 timezone 속성이 Asia/Seoul로 되어있었는데요. 이번엔 제거해보겠습니다.
그러면 예외가 발생하는데요. 함께 살펴보겠습니다. 이 예외는 spring.jackson.time-zone 설정여부와 무관합니다.
일단 예외가 발생하는 부분은 여기입니다. ZoneId가 null인 건데요. 이 부분은 @JsonFormat에 정답이 나와 있습니다.
tzStr 변수에 ##default라는 값이 있습니다. 그리고 DEFAULT_TIMEZONE과 값이 같으면 null을 반환합니다.
DEFAULT_TIMEZONE 역시 ##default 가 저장돼있습니다.
즉, @JsonFormat에 timezone 속성을 명시하지 않으면 timezone 변수에 null이 할당됩니다.
의문점
@JsonFormat의 timezone 속성이 지정되지 않았으면 직렬화할 때도 문제가 발생해야 할 것 같은데 직렬화할 때는 문제가 발생하지 않습니다. 왜 그럴까요? 이 문제 역시 디버깅을 통해 코드로 확인하실 수 있습니다.
InstantSerializerBase 클래스의 formatValue 메서드 내부에서 Zone이 null일 경우 그냥 값을 반환해버립니다.
정리
직렬화 과정에서 timezone이 지정되있지 않는 경우
- 현재 값을 그대로 반환합니다. ( 현재날짜를 반환 시 JVM Option값 'user.timezone' 을 참조합니다.)
직렬화 과정에서 timezone이 지정되있는 경우
- 우선순위는 @JsonFormat의 timezone 값, application.yaml의 spring.jackson.time-zone 값 순으로 적용됩니다.
역직렬화 과정에서 timezone이 지정되있지 않는 경우
- JSON Parse 예외가 발생됩니다.
역직렬화 과정에서 timezone이 지정되있는 경우
- DeserializationFeature의 ADJUST_DATES_TO_CONTEXT_TIME_ZONE 값이 true면 지정된 timezone을 무시하고 UTC로 변경됩니다.
- DeserializationFeature의 ADJUST_DATES_TO_CONTEXT_TIME_ZONE 값이 false이면 그대로 반환됩니다.
- application.yaml의 spring.jackson.time-zone에 정의된 zone에 맞게 변경됩니다.
'Spring' 카테고리의 다른 글
Spring WebFlux에서 Flux<String>을 응답할 때 JSON Array가 아닌 단일 String으로 응답되는 이슈 (0) | 2023.05.21 |
---|---|
AWS SES로 이메일 발송 시 파일첨부 기능 추가하기 (3) | 2023.04.09 |
Spring Cloud AWS SQS를 사용할 때 주의할 점 (1) | 2023.03.19 |
Spring Boot 2.4.x 이상 환경에서 AWS SQS를 이용한 애플리케이션 구축하기 (2) | 2023.03.13 |
Proxy기반으로 동작되는 어노테이션을 AspectJ 모듈과 함께 사용할 때 주의할 점 (0) | 2023.03.09 |