com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `클래스명` (no Creators, like default constructor, exist): cannot deserialize from Object value (no delegate- or property-based Creator) 에러
Spring에서 @RequestBody를 통해 데이터를 가져오는 중에 위와 같은 에러를 만났다.
구글링을 해보니 하나같이 빈 생성자를 생성하라고 하는데 대체 왜? 라는 생각이 들었다.
Spring은 Http Message Body를 읽기 위해
HttpMessageConverter를 사용한다.
클라이언트로부터 값을 받으면 여러 Converter 중에서 해당 값을 읽을 수 있는 Converter를 찾는다.
읽을 수 있는 컨버터를 찾으면 read() 메서드를 통해 값을 읽고 원하는 Object로 변환한다.
참고로 Spring에서 JSON의 형변환은 Jackson2HttpMessageConverter가 담당한다.
Jackson2HttpMessageConverter의 read() 메서드를 살펴보면 ObjectMapper를 통해 값을 변환시킨다.
+ json이 뭔지 모른다면? 👉 https://yeonyeon.tistory.com/48
👨💻 코드로 과정 살펴보기
1. HttpMessageConverter 목록 가져오기
아래는 @RequestBody에 설명된 주석의 일부를 가져왔다.
The body of the request is passed through an HttpMessageConverter to resolve the method argument depending on the content type of the request.
좀 더 구체적으로 들어가보자면 @RequestBody나 @ResponseBody를 처리를 도와주는 RequestResponseBodyMethodProcessor라는 클래스가 존재한다.
public abstract class AbstractMessageConverterMethodArgumentResolver implements HandlerMethodArgumentResolver {
// ...
@Nullable
protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter,
Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {
// ...
}
}
해당 메서드에서 converter 목록을 가져와 사용 가능한 converter인지 선택하는 로직이 존재한다.
2. MappingJackson2HttpMessageConverter 선택
여러가지 HttpMessageConverter 중에서 적합한 Converter를 찾는다.
Spring은 JSON의 경우 Jackson2HttpMessageConverter를 선택한다.
3. read() 호출
read()의 로직은 MappingJackson2HttpMessageConverter가 AbstractJackson2HttpMessageConverter에서 상속받았다.
jackson의 ObjectMapper를 호출해 값을 반환하는 것을 볼 수 있다.
@Override
public Object read(Type type, @Nullable Class<?> contextClass, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException {
JavaType javaType = getJavaType(type, contextClass);
return readJavaType(javaType, inputMessage);
}
private Object readJavaType(JavaType javaType, HttpInputMessage inputMessage) throws IOException {
ObjectMapper objectMapper = selectObjectMapper(javaType.getRawClass(), contentType);
// 실제 로직은 훨씬훨씬 복잡하지만 return시 ObjectMapper를 사용한다는 것만 알아두자.
return objectMapper.readValue(inputStream, javaType);
}
4. ObjectMapper가 JSON -> Object로 변환
public <T> T readValue(InputStream src, JavaType valueType)
throws IOException, StreamReadException, DatabindException
{
_assertNotNull("src", src);
return (T) _readMapAndClose(_jsonFactory.createParser(src), valueType);
}
protected Object _readMapAndClose(JsonParser p0, JavaType valueType)
throws IOException
{
// Object 반환
}
💡 ObjectMapper의 변환 과정
JSON을 인식 후 Object로의 변환 과정은 아래와 같다.
- Object 생성: 대부분 기본 생성자 이용. (예외: Property 사용 예제)
- Object 필드 인식: Setter 또는 Getter 이용
- Object 필드에 값 넣기: Reflection 이용 (setter 이용 X)
코드를 뜯어보며 좀 더 자세히 살펴보자.
0. 테스트 코드 작성
위에서 ObjectMapper의 readValue(InputStream, JavaType)이 호출되는 것을 확인했었다.
이제 테스트 코드를 작성하고 하나하나 디버깅하며 따라가보려 한다.
public class JsonTest {
@Test
void test() {
ObjectMapper mapper = new ObjectMapper();
String json = "{\"name\":\"yeonlog\"}";
InputStream inputStream = new ByteArrayInputStream(json.getBytes());
try {
User user = mapper.readValue(inputStream, User.class);
System.out.println(user);
} catch (Exception e) {
e.printStackTrace();
}
}
}
public class User {
private String name;
private User() {
}
public String getName() {
return name;
}
}
1. Object 생성
readValue()의 로직을 따라가다보면 아래 BeanDeserializer의 deserialize()를 호출한다.
2. Object 필드에 값 넣기
다시 BeanDeserializer의 vanillaDeserialize() 메서드로 돌아오자.
우선 JSON에서 key를 가져온다.
JSON에서 value를 가져온 뒤, 필드에 값을 set한다.
Field의 set() 메서드 로직에 대해서는 자세히 살펴보지는 않았다.
다만 reflect 패키지에 존재하는 클래스인 것 정도만 확인했다.
(Object 내에 존재하는 setter 메서드를 이용하지 않는다는 것을 증명하기 위해 확인하였음)
Package java.lang.reflect
Provides classes and interfaces for obtaining reflective information about classes and objects.
많은 삽질을 했는데 내가 맞게 디버깅했는지는 잘 모르겠다...T.T
이상한 점이나 예외사항이 있다면 댓글 부탁드립니다😂
참고
- https://jojoldu.tistory.com/407
- https://velog.io/@conatuseus/RequestBody%EC%97%90-%EA%B8%B0%EB%B3%B8-%EC%83%9D%EC%84%B1%EC%9E%90%EB%8A%94-%EC%99%9C-%ED%95%84%EC%9A%94%ED%95%9C%EA%B0%80
- https://velog.io/@park2348190/Jackson-ObjectMapaer%EC%97%90%EC%84%9C-%EA%B8%B0%EB%B3%B8-%EC%83%9D%EC%84%B1%EC%9E%90-%EC%97%86%EC%9D%B4-Deserialization-%ED%95%98%EA%B8%B0
'Develop > Spring+JPA' 카테고리의 다른 글
[Spring 5 프로그래밍 입문] chapter 2 - 스프링 시작하기 (0) | 2022.05.21 |
---|---|
[Spring] Spring의 트랜잭션 관리 (feat: @Transactional) (6) | 2022.05.08 |
[Spring] 생성자 주입 vs 필드 주입 vs 수정자 주입 (0) | 2022.04.28 |
[Spring] @ExceptionHandler로 API 예외 한번에 처리하기 (7) | 2022.04.21 |
[Spring Security] 초간단 로그인 만들기 (2) | 2022.03.07 |