5장에서 Kotlin 에서 @RequsetBody 에 기본 생성자가 필요없다라는 것에 대해 알아봤습니다.
이 과정에서 찜찜한 상태로 넘어갔었습니다.
이번에 새롭게 알게된 것으로 이를 해결했기에 기록해봅니다.
1. Jackson-Module-Kotlin
https://proandroiddev.com/parsing-optional-values-with-jackson-and-kotlin-36f6f63868ef
이전 글에서, Json 을 @RequestBody에서 코틀린으로 된 Dto 로 변환할때, kotlinValueInstantiator 라는 것이 있었습니다.
이 클래스가 바로 Jackson Module Kotlin 안에 있었습니다.
이 모듈은 본인은 Intellij 의 Spring Initializer 를 이용하여 Spring Web Mvc 를 생성하면 자동으로 의존성이 추가되었습니다.
위의 포스팅을 보면 Jackson Kotlin Module 을 사용하는 이유는 아래입니다.
- Data Class 는 빈 생성자를 빈 생성자를 가질 수 없다.
- Primary Constructor 를 사용한 일반 Class 는 빈 생성자가 없다.
따라서 아래의 조치를 취해야 했던 겁니다.
- Primary Constructor 를 사용하지 않은 Class 사용. (빈 생성자가 자동으로 생기게)
- @JsonProperty 를 명시적으로 붙이기.
이를 개선한 방향으로 Jackson Module Kotlin 을 사용하는 것입니다.
이 모듈 덕분에 Kotlin 은 코틀린의 Primary 생성자를 인식하고 사용할 수 있게 됩니다.
https://github.com/FasterXML/jackson-module-kotlin/blob/master/README.md
https://github.com/FasterXML/jackson-module-kotlin
2. Jackson-Module-Parameter-names
하지만 의문점은 풀리지 않았습니다. 프로젝트에서 위의 Jackson-Module-Kotlin 의존성을 제거하고 돌려봐도 기본생성자 없이 정상적으로 작동했기 때문입니다.
디버그 해보니 위의 경우와 다르게 kotlinValueInstantiator 없이도 잘 돌아갔습니다. (모듈 의존성이 없어졌으니 당연...)
다른 무언가가 있나 하고 파보니, 위의 모듈이 정답이었습니다.
2-1. JDK 8 의 Reflection API
http://openjdk.java.net/jeps/118
위의 내용에 따르면 JDK 8 버전 이전에는 Reflection 으로 파라미터 이름을 가져올 수 없었으나, JDK 8 부터 해당 기능이 추가되었다고 합니다.
해당 API 를 사용하는 방법은 컴파일 타임에 컴파일 옵션으로 -parameters 옵션을 주면 되며, Spring Boot Gradle Plugin 에서 컴파일시 자동으로 붙여준다고 합니다.
2-2. Jackson-Module-Parameter-names
https://github.com/FasterXML/jackson-modules-java8
Spring boot 의 Web 의존성을 그대로 사용하면 spring boot stater 에서 Json Deserialize 에 Jacson 라이브러리가 사용됨을 잘 알고 있을 것입니다.
JacksonAutoConfiguration 클래스를 보면 다음의 내용이 정의되어 있습니다.
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(ObjectMapper.class)
public class JacksonAutoConfiguration {
...
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(ParameterNamesModule.class)
static class ParameterNamesModuleConfiguration {
@Bean
@ConditionalOnMissingBean
ParameterNamesModule parameterNamesModule() {
return new ParameterNamesModule(JsonCreator.Mode.DEFAULT);
}
}
...
}
parameterNames 라는 모듈이 Jackson 에 등록되어 사용되네요.
그럼 Parameter Names 모듈을 들여다 봅시다.
package com.fasterxml.jackson.module.paramnames;
class ParameterExtractor implements java.io.Serializable {
private static final long serialVersionUID = 1L;
public Parameter[] getParameters(Executable executable) {
return executable.getParameters();
}
}
executable.getParameters() 로 파라미터들을 얻어오는 로직을 발견할 수 있습니다.
Executable 을 타고 들어가면, Java 1.8 부터 생성된 Consturctor 정보를 얻는 Reflection API 라고 합니다.
package java.lang.reflect;
/**
* A shared superclass for the common functionality of {@link Method}
* and {@link Constructor}.
*
* @since 1.8
*/
public abstract class Executable extends AccessibleObject
implements Member, GenericDeclaration {
결과적으로, Jackson-Kotlin-Module 없이도 Java Reflection 을 통해 빈 생성자 없이 @RequestBody Deserialize 를 진행할 수 있다 라고 알 수 있습니다.
정리해보면,
Jackson-Kotlin-Module 은 Spring Initializer 를 통해 Spring Web MVC 를 생성하면 자동으로 추가됩니다. 따라서 build.gradle 에서 의존성을 삭제하면 뺄 수 있습니다.
반면, Jackson-Module-Parameter-names 모듈은 Spring-Boot-Starter-Web 안의 Jackson 에 묶여있는 모듈이므로, 추가라기 보다는 기본적으로 들어오는 스타터 모듈입니다.
두 모듈 모두, 코틀린에서 기본 생성자 없이 (@RequestBody 등에서) Deserialize 를 진행할 수 있게 해줍니다.
두 모듈이 모두 존재할 경우, Jackson-Kotlin-Module 이 우선적으로 사용됩니다.
*추가*
https://github.com/FasterXML/jackson-modules-java8/tree/master/parameter-names
마지막을 보면
if class Person has a single argument constructor, its argument needs to be annotated with @JsonProperty("propertyName"). This is to preserve legacy behavior, see FasterXML/jackson-databind/#1498
이라는 문구가 있다.
https://github.com/FasterXML/jackson-databind/issues/1498
인수가 여러개인 생성자인 경우는 잘 동작했지만, 단일 인수를 가진 생성자인 경우 @JsonCreator 를 명시적으로 붙여야 했다가, 2020/9 월에 이 문제가 해결되어 닫힌 이슈다.
어 나는 최근에 기본 생성자 없으니 에러나던데 ? 라는 물음에는 인자가 하나이지 않았을까~ 그것도 현재는 해결이 되었지만 이라는 답을 할 수 있을 것 같다.
참고.
https://cheese10yun.github.io/spring-kotlin/
'Kotlin' 카테고리의 다른 글
코틀린과 Hibernate, CGLIB, Proxy 오해와 재대로된 사용법 - (5) (1) | 2021.09.07 |
---|---|
코틀린과 Hibernate, CGLIB, Proxy 오해와 재대로된 사용법 - (4) (0) | 2021.09.06 |
코틀린과 Hibernate, CGLIB, Proxy 오해와 재대로된 사용법 - (3) (0) | 2021.09.06 |
코틀린과 Hibernate, CGLIB, Proxy 오해와 재대로된 사용법 - (2) (0) | 2021.09.06 |
코틀린과 Hibernate, CGLIB, Proxy 오해와 재대로된 사용법 - (1) (0) | 2021.09.06 |