본문 바로가기

Kotlin

코틀린과 Hibernate, CGLIB, Proxy 오해와 재대로된 사용법 - (5)

이번에는 전 글에서 언급했듯이,

 

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://velog.io/@conatuseus/RequestBody%EC%97%90-%EC%99%9C-%EA%B8%B0%EB%B3%B8-%EC%83%9D%EC%A0%95%EC%9E%90%EB%8A%94-%ED%95%84%EC%9A%94%ED%95%98%EA%B3%A0-Setter%EB%8A%94-%ED%95%84%EC%9A%94-%EC%97%86%EC%9D%84%EA%B9%8C-2-ejk5siejhh

 

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