단일 필드 객체의 @RequestBody 생성 불가 문제

선종우·2023년 7월 24일
0

1. 배경

프로젝트 진행 도중 단일 필드 및 생성자를 가진 객체에 대한 @RequestBody가 정상적으로 작동하지 않았다.

2. @RequestBody 애노테이션 흐름 분석

|HandlerMethodArgumentResolverComposilte) resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory)  argument에 대한 resolving 시작지점 이때 RequestBodyRequestResponseBdoyMethodProcessor
-->|RequestResponseBdoyMethodProcessor) Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());
---->|RequestResponseBdoyMethodProcessor) Object arg = readWithMessageConverters(inputMessage, parameter, paramType);
------>|(AbstractMessageConverterrMethodArgumentResolver) RequestResponseBdoyMethodProcessor) genericConverter.canRead(targetType, contextClass, contentType)
-------->|AbstractJacson2HttpMessageConverter) objectMapper.canDeserialize(javaType, causeRef)
---------->|objectMapper) return createDeserializationContext(null, getDeserializationConfig()).hasValueDeserializerFor(type, cause);
------------>|DesirializationContect) return _cache.hasValueDeserializerFor(this, _factory, type); -> cache에 deserializer가 없으면 생성 있으면 반호나
-------------->|DeserializerCache) deser = _createAndCacheValueDeserializer(ctxt, factory, type)  -> cache에 없을 경우 생성구문으로 넘어감
---------------->|DeserializerCache) return _createAndCache2(ctxt, factory, type);
------------------>|DeserializerCache) deser = _createDeserializer(ctxt, factory, type);
-------------------->|DeserializerCache)return (JsonDeserializer<Object>) _createDeserializer2(ctxt, factory, type, beanDesc);
---------------------->|DeserializerCache)return factory.createBeanDeserializer(ctxt, type, beanDesc);
------------------------>|BeanDeserializerFactory) return buildBeanDeserializer(ctxt, type, beanDesc);
-------------------------->|BeanDeserializerFactory) valueInstantiator = findValueInstantiator(ctxt, beanDesc);  -> ) _fromStringCreator에 생성자가 붙은 initiator 반환
---------------------------->|BasicDeserializerFactory) instantiator = _constructDefaultValueInstantiator(ctxt, beanDesc);
------------------------------>|BasicDeserializerFactory) _addImplicitConstructorCreators(ctxt, ccState, ccState.implicitConstructorCandidates())  -> 중요
-------------------------------->|BasicDeserializerFactory) _handleSingleArgumentCreator(creators,ctor, false,vchecker.isCreatorVisible(ctor));
---------------------------------->|BasicDeserializerFactory) creators.addStringCreator(ctor, isCreator);
------------------------------------>|CreatorCollector) verifyNonDup(creator, C_STRING, explicit);  -> _creator[1] = String형 할당
------------------------------>|BasicDeserializerFactory) return ccState.creators.constructValueInstantiator(ctxt);
-------------------------------->|CreatorCollector) inst.configureFromObjectSettings(_creators[C_DEFAULT], _creators[C_DELEGATE],delegateType, _delegateArgs, _creators[C_PROPS],_propertyBasedArgs); 만약 프로퍼티 기반 creator라면 여기서  _withArgsCreator가 setting됨 그러나 single arg constructor의 경우 여기서 null이 세팅됨
-------------------------------->|CreatorCollector) inst.configureFromStringCreator(_creators[C_STRING]); -> creator 타입에 따라 creator 생성 
------------------>|DeserializerCache) ((ResolvableDeserializer)deser).resolve(ctxt);  --> 확보된 deserializer로 object 생성 시도
-------------------->|BeanDeserializerBase) _valueInstantiator.canCreateFromObjectWith() 이때 _withArgsCreator가 null인지 check하는데, _creator[8] == null이면  null---------------------->|StdValueInstantiator) return (_withArgsCreator != null);
-------------------->|BeanDeserializerBase) if (creatorProps != null) {_propertyBasedCreator = PropertyBasedCreator.construct(ctxt, _valueInstantiator,creatorProps, _beanProperties); }  -> creatorProps == null이기 때문에 propertyBasedCreator 생성 안 됨
-------->|AbstractJacson2HttpMessageConverter) body = (genericConverter != null ? genericConverter.read(targetType, contextClass, msgToUse) : (HttpMessageConverter<T>) converter).read(targetClass, msgToUse));
---------->|AbstractJacson2HttpMessageConverter) return readJavaType(javaType, inputMessage);
------------>|AbstractJacson2HttpMessageConverter) InputStream inputStream = StreamUtils.nonClosing(inputMessage.getBody());
------------>|AbstractJacson2HttpMessageConverter) return objectMapper.readValue(inputStream, javaType);
-------------->|ObjectMapper) return (T) _readMapAndClose(_jsonFactory.createParser(src), valueType);
---------------->|ObjectMapper)  result = ctxt.readRootValue(p, valueType,_findRootDeserializer(ctxt, valueType), null);
------------------>|DefaultDeserializationContext)   return deser.deserialize(p, this);
-------------------->|BeanDeserializer)return deserializeFromObject(p, ctxt);
---------------------->|BeanDeserializer) Object bean = deserializeFromObjectUsingNonDefault(p, ctxt);
------------------------>|BeanDeserializerBase) if (_propertyBasedCreator != null) {return _deserializeUsingPropertyBased(p, ctxt);} propertyBasedCreator가 null이므로 deserialize를 진행하지 앟음
------------------------>|BeanDeserializerBase)  return ctxt.handleMissingInstantiator(raw, getValueInstantiator(), p,"cannot deserialize from Object value (no delegate- or property-based Creator)");
  • 단일 필드 및 생성자 객체의 경우 필드가 여러개인 객체와 다른 방식으로 작동한다.
  • 단일 생성자 방식의 경우 @AllArgsConstructor를 설정할 거면 @Setter도 같이 설정해야 한다.
  • 아니면 @NoArgsContructor + @Getter조합으로 객체 생성을 하게끔 해야 한다.

1개의 댓글

comment-user-thumbnail
2023년 7월 24일

좋은 정보 얻어갑니다, 감사합니다.

답글 달기