이번에는 전 글에서 언급했듯이,
Kotlin + @RequestBody 에서 기본생성자가 필요할까?
에 대해 알아보도록 하겠습니다.
1. Converter
@RequestBody 로 객체를 맵핑하는 방법은, 적절한 컨버터를 찾아 read() 하는 것입니다.
AbstractMessageConverterMethodArgumentResolver 클래스를 봅시다.
그 중 readWithMessageConverters 를 봅시다.
적절한 post 요청으로 디버그를 해봤습니다.
messageConverters 에 10개의 컨버터가 대기중이군요.
예상컨데, 우리는 Json 요청을 보낼 것이니 MappingJackson2HttpMessageConverter 를 쓸 것입니다.
위의 루프문을 돌면서,
MappingJackson2HttpMessageConverter 해당 클래스가 GenericHttpMessageConverter 의 하위 클래스이므로 동작이 수행됨을 확인하였습니다.
이제 canRead() 인지 확인하고 아래에서 본격적으로 read() 메서드를 통해 값을 읽어와서 객체에 할당할 것 같군요.
read() 를 따라가 봅시다.
AbstractJackson2HttpMessageConverter 클래스에서 찾을 수 있습니다.
readJavaType 을 호출하는군요.
위 코드를 보면 ObjectMapper 가 나오기 시작합니다.
ObjectMapper 클래스를 이용하여 readValue() 를 하는군요.
우리 코드는 UniCode 가 true 이므로,
objectMapper.readValue(inputMessage.getBody(), javaType) 이 호출될 것입니다.
2. ObjectMapper
계속 따라가봅시다.
이제 나오는 이 _readMapAndClose() 부터가 중요한데요,
중간의 브레이크포인트를 보면
- _findRootDeserializer() 를 통해 어떤 역직렬화 해주는 객체를 찾고,
- 그 Deserializer 를 통해 값을 읽어오는 듯 합니다.
_findRootDeserializer() 를 돌아가는 것을 보면 Java와는 다른 Kotlin 의 차이점을 볼 수 있습니다.
deserializer 의 _valueInstantiator 에 KotlinValueInstantiator 가 들어있습니다.
이를 생각하면서, 이후에 어떤 차이가 있을지 봅시다.
이제 _valueInstantiator 가 KotlinValueInstantiator 인 deserializer 을 가지고
readRootValue() 를 수행하러 갑니다.
가지고 있던 deserializer 를 가지고 역직렬화를 통해 값을 읽어오는 일을 수행하네요.
결과적으로는 deserializeFromObject(p, ctxt) 를 수행합니다.
그 전체 코드는 아래입니다.
package com.fasterxml.jackson.databind.deser;
public class BeanDeserializer
extends BeanDeserializerBase
implements java.io.Serializable
{
...
/**
* General version used when handling needs more advanced features.
*/
@Override
public Object deserializeFromObject(JsonParser p, DeserializationContext ctxt) throws IOException
{
/* 09-Dec-2014, tatu: As per [databind#622], we need to allow Object Id references
* to come in as JSON Objects as well; but for now assume they will
* be simple, single-property references, which means that we can
* recognize them without having to buffer anything.
* Once again, if we must, we can do more complex handling with buffering,
* but let's only do that if and when that becomes necessary.
*/
if ((_objectIdReader != null) && _objectIdReader.maySerializeAsObject()) {
if (p.hasTokenId(JsonTokenId.ID_FIELD_NAME)
&& _objectIdReader.isValidReferencePropertyName(p.currentName(), p)) {
return deserializeFromObjectId(p, ctxt);
}
}
if (_nonStandardCreation) {
if (_unwrappedPropertyHandler != null) {
return deserializeWithUnwrapped(p, ctxt);
}
if (_externalTypeIdHandler != null) {
return deserializeWithExternalTypeId(p, ctxt);
}
Object bean = deserializeFromObjectUsingNonDefault(p, ctxt);
/* 27-May-2014, tatu: I don't think view processing would work
* at this point, so commenting it out; but leaving in place
* just in case I forgot something fundamental...
*/
/*
if (_needViewProcesing) {
Class<?> view = ctxt.getActiveView();
if (view != null) {
return deserializeWithView(p, ctxt, bean, view);
}
}
*/
return bean;
}
final Object bean = _valueInstantiator.createUsingDefault(ctxt);
// [databind#631]: Assign current value, to be accessible by custom deserializers
p.setCurrentValue(bean);
if (p.canReadObjectId()) {
Object id = p.getObjectId();
if (id != null) {
_handleTypedObjectId(p, ctxt, bean, id);
}
}
if (_injectables != null) {
injectValues(ctxt, bean);
}
if (_needViewProcesing) {
Class<?> view = ctxt.getActiveView();
if (view != null) {
return deserializeWithView(p, ctxt, bean, view);
}
}
if (p.hasTokenId(JsonTokenId.ID_FIELD_NAME)) {
String propName = p.currentName();
do {
p.nextToken();
SettableBeanProperty prop = _beanProperties.find(propName);
if (prop != null) { // normal case
try {
prop.deserializeAndSet(p, ctxt, bean);
} catch (Exception e) {
wrapAndThrow(e, bean, propName, ctxt);
}
continue;
}
handleUnknownVanilla(p, ctxt, bean, propName);
} while ((propName = p.nextFieldName()) != null);
}
return bean;
}
여기서 주목할 부분은 아래 부분입니다.
if (_nonStandardCreation) {
if (_unwrappedPropertyHandler != null) {
return deserializeWithUnwrapped(p, ctxt);
}
if (_externalTypeIdHandler != null) {
return deserializeWithExternalTypeId(p, ctxt);
}
Object bean = deserializeFromObjectUsingNonDefault(p, ctxt);
return bean;
}
// 주석은 삭제함.
이제 우리가 원하던 무언가를 찾은 것 같습니다.
_nonStandardCreation 이 true 이면
deserializeFromObjectUsingNonDefault() 즉, Non-default 생성자를 통해 객체를 생성하는 로직
입니다.
그 아래 코드를 보면
final Object bean = _valueInstantiator.createUsingDefault(ctxt);
우리가 알던 대로, Default 생성자를 통해 객체를 만드는 코드가 바로 아래 있습니다.
그러니까, java 와 다르게 kotlin 에서 _nonStandardCreation 를 true 로 만드는 코드만 찾으면,
kotlin 은 기본생성자가 필요없다 ! 가 되겠군요.
3. KotlinValueInstantiator
위에서 길다란 코드가 있었죠.
public class BeanDeserializer extends BeanDeserializerBase
여기서 우리는 deserailize 를 했었구요.
그리고 찾고 싶었던 것은 '_nonStandardCreation 이 true 인가' 였고,
이를 초기화하는 코드는 BeanDeserializer 의 상위 클래스 BeanDeserializerBase 에 있습니다.
or 로 엮여 있으니 저 세 조건 중 하나만 true 면
-> _nonStandardCreation 가 true 일 것이고
-> default 생성자 없이도 객체 생성이 가능하겠네요.
결론은 가운데 _valueInstantiator.canCreateFromObjectWith() 이 true 입니다.
_valueInstantiator 은 KotlinvalueInstantiator 라고 했었죠.
그리고 KotlinvalueInstantiator 는 StdValueInstantiator 를 상속하고 있습니다.
@JacksonStdImpl
public class StdValueInstantiator
extends ValueInstantiator
implements java.io.Serializable
{
...
// 생성자
protected StdValueInstantiator(StdValueInstantiator src)
{
...
_withArgsCreator = src._withArgsCreator;
...
}
...
}
그렇다면, KotlinvalueInstantiator 의 canCreateFromObjectWith()
또는 StdValueInstantiator 의 canCreateFromObjectWith() 를 찾으면 되겠네요.
KotlinvalueInstantiator 에는 없고, 상위 StdValueInstantiator 의 canCreateFromObjectWith() 를 사용하고 있습니다.
아까 말했듯, _withArgsCreator 가 notNull 이고, true 가 반환되었군요.
그래서 _nonStandardCreation 가 true 가 될 수 있었고, default 생성자가 없이도 코드가 동작한 것입니다.
4. 결론
사실 이번 결론은 찜찜하긴 합니다.
Instantiator 라는 것이 언제 생성되고, 어떤 역할을 하는지 정확하게 파악하지 못하여
Java 에서 Instantiator 는 _nonStandardCreation 를 false 를 가지는 이유에 대해 모르기 때문입니다.
해당 부분에서는 계속 공부해야할 예정이긴 하지만
일단 결론은 Kotlin 에서 @ReqeustBody 는 기본 생성자가 필요없다 ! 입니다.
참고
https://proandroiddev.com/parsing-optional-values-with-jackson-and-kotlin-36f6f63868ef
https://github.com/FasterXML/jackson-module-kotlin/blob/master/README.md
https://github.com/FasterXML/jackson-module-kotlin
'Kotlin' 카테고리의 다른 글
코틀린과 Hibernate, CGLIB, Proxy 오해와 재대로된 사용법 - (6) (0) | 2021.09.30 |
---|---|
코틀린과 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 |