1. LocalDate 직렬화 - 기본 serizlizer 가 objectMapper 에 등록되어 있고, 또 다른 포멧으로 직렬화 해야 하는 요구사항이 있다면 어떻게 해야하나?
Spring boot 의 Jackson 라이브러리로는 LocalDate, LocalDateTime 타입이 이상적으로 직렬화되지 않는다.
따라서 아래와 같이 @JsonFormat 을 붙여서 직렬화, 역직렬화 시 패턴을 지정할 수 있다.
@JsonFormat(pattern = "yyyy-MM-dd")
val createAt: LocalDate
모든 LocalDate, LocalDateTime 에 @JsonFormat 을 붙이기는 불편하다.
Serializer 를 직접 생성하여 ObjectMapper 에 등록하는 방법이 있다.
@Configuration
class BeanConfig {
@Bean
fun getObjectMapper(): ObjectMapper {
val simpleModule = SimpleModule().apply {
addSerializer(LocalDate::class.java, LocalDateSerializer())
addDeserializer(LocalDate::class.java, LocalDateDeserializer())
}
ObjectMapper().apply {
registerModules(simpleModule)
}
}
}
class LocalDateSerializer : JsonSerializer<LocalDate> {
override fun serialize(date: LocalDate, g: JsonGenerator, sp: SerializerProvider) {
val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd")
val format = dateTime.format(formatter)
g.writeString(format)
}
}
LocalDate, LocalDateTime 을 사용하는 프로젝트라면, 보통 기본적으로 이 기본 직렬화, 역직렬화 클래스가 ObjectMapper 에 등록되어 있을 것이다.
이 때, 내가 다른 방법으로 직렬화를 하고 싶어서, 특정 클래스에 @JsonFormat 을 붙여도, 먹히지 않는다. 이 때에는 @JsonFormat 말고 @JsonSerialize 를 선언하여 직렬화 클래스를 선언해줘야 한다.
class LocalDateCustomSerializer : JsonSerializer<LocalDate> {
override fun serialize(date: LocalDate, g: JsonGenerator, sp: SerializerProvider) {
val formatter = DateTimeFormatter.ofPattern("yyyyMMdd")
val format = dateTime.format(formatter)
g.writeString(format)
}
}
@JsonSerialize(using = LocalDateCustomSerializer::class)
val createAt: LocalDate
// 출력 형식 : 20211118
2. rest API 서버에서 404 상황 시 공통 response 처리 방법을 알아보다가 정리한 점.
dispatcher.setThrowExceptionIfNoHandlerFound(true)
이 옵션은 doDispatch 의 mappedHandler 가 null 일 때, 즉 handler 를 찾지 못했을 때, 즉 404일 떄 noHandlerFound 메서드를 실행시키게 되는데, 이 noHandlerFound 메서드에서 ThrowExceptionIfNoHandlerFound 값이 true 일 때 NoHandlerFoundException 예외 를 던지게 해줌. 따라서 @ExceptionHandler 로 잡을 수 있음.
그런데, WebMvcConfigurer 구현 클래스에서
override fun configureDefaultServletHandling(configurer: DefaultServletHandlerConfigurer) {
configurer.enable()
}
로 해버리면, DefaultServletHttpRequestHandler, SimpleUrlHandlerMapping 을 등록시켜버려서, mappedHandler가DefaultServletHttpRequestHandler 가 들어와서 null 이 아니게 되어서 예외가 발생 안함.
따라서, enable() 설정을 껐더니 에러가 잘 발생했고 @ExceptionHandler 에서 원하던 처리를 할 수 있었다.
그런데, 여기서 @RequestMapping("/**") 라는 컨트롤러를 설정해주게 되면, 뭔가 mappedHandler 가 들어왔다. 마치 enable() 설정을 킨 것 처럼. 따라서, enable() 을 없앴다 하더라도, 저런 컨트롤러가 있으면 예외가 발생하지 않았다. 그리고 그 핸들러가 404 상황에 대해 처리해주고 있었다. 뭔가 핸들러를 찾지 못했을때 DefaultServlet 처럼 기본 처리를 하는 로직인 것 같은데, (마치 디폴트 서블릿 = @RequestMapping("/") 인 것 처럼) 결국 해답은 찾지 못했다. 그리고 우선순위는 @RequestMapping("/") 의 처리가 DefaultServlet 처리보다 먼저였다.
문제상황의 결론은, 404 상황에 대한 처리로 커스텀한 @RequestMapping("/") 를 만들기로 했다.
->
@RequestMapping("/**") 라는 것은 모든 URL 에서 핸들러를 찾을 수 있다는 말과 같다.
따라서 dispatcherServlet 의 mappedHandler 가 null 일 리가 없다.
결국 @RequestMapping("/**") 가 있다면, DefaultServelt 이 필요없다는 말과 같다. 핸들러를 찾지 못하는 notfound 상황이 없으니까.
단지,@RequestMapping("/**") 와 @RequestMapping("/book") 과 같이 특정지은 핸들러가 있을 때, 둘 다 해당할텐데, 더 비슷한 이름의 핸들러 엔드포인트를 찾는 로직을 아직 모른다. 더 똑같은걸 선택하는 건 당연하지만, 구체 로직이 궁금하긴 하다.
3. configureDefaultServletHandling
override fun configureDefaultServletHandling(configurer: DefaultServletHandlerConfigurer) {
configurer.enable()
}
이 코드는 아래 두 클래스를 빈으로 등록시킨다.
- DefaultServletHttpRequestHandler
- SimpleUrlHandlerMapping
~Handler 는 컨트롤러라 생각하면 쉬운, 실제 요청을 처리하는 핸들러이다.
~Mapping 은 요청에 맞는 요청을 처리할 핸들러에 맵핑시킨다.
위 의 enable() 설정으로 인해, Dispatcher Servlet 은 요청을 아래와 같은 순서로 처리한다.
- RequestMappingHandlerMapping 을 사용해서 요청을 처리할 핸들러를 검색한다.
- 존재하면 해당 핸들러를 이용해서 요청을 처리한다.
- 존재하지 않으면 SimpleUrlHandlerMapping 을 사용해서 요청을 처리할 핸들러를 검색한다.
- SimpleUrlHandlerMapping 은 모든 경로("/**")에 대해 DefaultServletHttpRequestHandler를 리턴한다. 즉 모든 경로에서 DefaultServletHttpRequestHandler 를 사용한다는 것이다.
- DispatcherServlet 은 DefaultServletHttpRequestHandler 에 처리를 요청한다.
- DefaultServletHttpRequestHandler 는 디폴트 서블릿에 처리를 위임한다.
여기서, 디폴트 서블릿은, 톰켓에 정의된 기본 디폴트 페이지를 보여줄 수도 있고, 또는 따로 설정한 프로젝트의 페이지일 수 도 있다.
'TIL' 카테고리의 다른 글
TIL) 객체의 커스텀 직렬화 (0) | 2021.11.21 |
---|---|
TIL) 터미널에서 sftp 접속, Jackson2HttpMessageConverter (0) | 2021.11.20 |
TIL) ParameterNamesModule 이슈, 에너테이션 클래스에서 사용 가능한 타입, 전체 프로젝트 의존성 트리 (0) | 2021.11.12 |
TIL) KClass, Sealed class, 고차 함수: 함수 타입, invoke (0) | 2021.11.09 |
TIL) JWT 와 보안, CORS, 카카오에서 CORS (0) | 2021.11.07 |