2024. 2. 11. 15:34ㆍJava
이슈 내용
환경: JDK 1.8, Spring Boot 2.7.x
Controller에서 @RequestParam
으로 ZonedDateTime(”2024-01-01T00:00:00.000+09:00”)
을 요청 받을 경우 parsing 에러 발생
에러 로그
.w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.method.annotation.MethodArgumentTypeMismatchException: Failed to convert value of type 'java.lang.String' to required type 'java.time.ZonedDateTime'; nested exception is org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.String] to type [@org.springframework.web.bind.annotation.RequestParam java.time.ZonedDateTime] for value '2024-01-01T00:00:00.000+09:00'; nested exception is java.lang.IllegalArgumentException: Parse attempt failed for value [2024-01-01T00:00:00.000+09:00]]

컨트롤러
@RestController
@RequestMapping("/api/v1")
public class TestController {
@GetMapping
public TestRs findCurrentTime(@RequestParam ZonedDateTime date) {
return new TestRs("response", date);
}
}
실행한 테스트 코드
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class TestControllerTest {
@LocalServerPort
int port;
@BeforeEach
public void setup() {
RestAssured.port = port;
}
@Test
public void testFindCurrentTime() {
String date = "2024-01-01T00:00:00.000+09:00";
given()
.queryParam("date", date)
.when()
.get("/api/v1")
.then()
.statusCode(200)
.contentType(ContentType.JSON)
.body("message", equalTo("response"))
.body("date", equalTo(date));
}
}
원인 분석
이러한 현상에 대해 디버깅을 돌려보면, java.time
모듈 내 DateTimeFormatter
에서 parsing 처리를 하고 있는 것을 알 수 있다.
어디서 parsing이 이루어지는 지 몰랐기에, DispatcherServlet
부터 빠르게 넘겨가며 찾을 수 있었다.


DateTimeFormatter
의 printParser
는 CompositePrinterParser
(DateTimePrinterParser
구현체) 이고, parse(DateTimeParseContext context, CharSequence text, int position)
메서드를 확인해보면 다음과 같습니다.

아무런 설정하지 않은 상태에서의 optional
값은 false
이므로, 네모박스 영역의 로직에서 실제 parsing이 일어납니다.
여기서, CompositePrinterParser
가 가진 DateTimePrinterParser[] printerParsers
에 따라 parsing이 결정됩니다.
디버깅을 통해 확인해보면 다음과 같습니다.

이후 LocalizedPrintParser
구현체의 parse(DateTimeParseContext context, CharSequence text, int position)
메서드 내에서 formatter(context.getLocale(), chrono).toPrinterParser(false)
부분이 다음과 같은 printerParsers
를 가진 CompositePrintParser
를 반환합니다.


이대로 진행하는 경우, 2024-01-01T00:00:00.000+09:00
입력에 대해서 위 이미지에서 보이는 printParsers[1]
인 CharLiteralPrintParser
가 202 부분을 처리하는 과정에서 에러가 발생합니다.
결국, java.time
모듈에서 기본적으로 설정되는 ZonedDateTime
관련 포맷팅이 우리가 기대한 것과 다른 것을 알 수 있습니다.
이에 대한 해결책은 여러 개발자 분들의 포스팅을 통해 도움을 받았습니다. (문서 하기 링크 참고)
@RequestParam
인자에@DateTimeFormatter
추가WebMvcConfigurer
구현체를 통해 registry 추가
해결책 1. @RequestParam
인자에 @DateTimeFormatter
추가
다음과 같이 특정 컨트롤러 메서드 인자에 @DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX")
를 추가해주는 것입니다.
@GetMapping
public TestRs findCurrentTime(@RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX") ZonedDateTime date) {
return new TestRs("response", date);
}
다음과 같이 printerParser
가 구성되며, 테스트가 정상적으로 통과합니다.

해결책 2. WebMvcConfigurer
구현체를 통해 커스텀 Converter 추가
다음과 같이 WebMvcConfigurer
를 구현한 Config 클래스에 String
→ ZonedDateTime
변환하는 역할을 하는 Converter
구현체를 추가합니다.
다음과 같이 WebMvcConfigurer
를 구현한 Config 클래스에 String
→ ZonedDateTime
변환하는 역할을 하는 Converter
구현체를 추가합니다.
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new StringToZonedDateTimeConverter());
}
}
public class StringToZonedDateTimeConverter implements Converter<String, ZonedDateTime> {
@Override
public ZonedDateTime convert(String source) {
return ZonedDateTime.parse(source, DateTimeFormatter.ISO_DATE_TIME);
}
}
다음과 같이 printerParser
가 구성되며 정상적으로 통과하는 것을 확인할 수 있습니다.


참고 링크
'Java' 카테고리의 다른 글
성능 이슈 발생한 무거운 API 튜닝하기 (0) | 2023.05.14 |
---|