@RequestBody에 왜 기본 생성자는 필요하고, Setter는 필요 없을까? #3

사명기·2020년 2월 18일
12

이전 글에서는 RestController에서 @RequestBody 바인딩을 Jackson 라이브러리의 ObjectMapper가 하는 것을 확인했습니다.
그리고 RequestBody를 생성할 때, DTO가 Property기반이 아니거나 Delegate를 한 상태가 아니라면 기본 생성자로 생성한다는 것을 알아냈습니다.
이번 글에서는 기본 생성자로 생성하는데, 어떻게 해당 DTO에 Setter가 필요없지? 라는 의문점을 풀어보도록 하겠습니다.

저는 RequestBody로 들어오는 RequestDto에 위와 같이 @NoArgsConstructor, Getter 어노테이션만 작성했습니다.

위와 한다면 Setter가 없는데 스프링이 어떻게 DTO에 값을 넣어줄까 의문을 가졌습니다. 이 문제를 해결하기 위해 ObjectMapper에 대해 좀 더 알아볼 필요가 있었습니다.

어떻게 ObjectMapper가 JSON Field와 Java Field를 매칭시킬까?

이를 알아보기 위해 여기를 참고했습니다.

이를 해석해 보면, 다음과 같습니다.

기본적으로 Jackson은 JSON 필드의 이름을 Java 오브젝트의 getter 및 setter 메소드와 일치시켜 JSON 오브젝트의 필드를 Java 오브젝트의 필드에 매칭합니다. Jackson은 getter 및 setter 메소드 이름의 "get"및 "set"부분을 제거하고 나머지 이름의 첫 문자를 소문자로 변환합니다.
예를 들어 brand라는 JSON 필드는 getBrand () 및 setBrand ()라는 Java getter 및 setter 메소드와 일치합니다. engineNumber라는 JSON 필드는 getEngineNumber () 및 setEngineNumber ()라는 getter 및 setter와 일치합니다.

쉽게말해 동일한 이름의 변수를 직접 찾는 것이 아니라, Getter와 Setter를 통해 찾아서 매칭한다는 것입니다.

DTO에 필드 값을 주입하는 과정을 살펴보자

변수의 매칭은 getter와 setter를 하는 것을 알아냈습니다. 그럼 그렇게 매칭한 것을 어떻게 주입시키는지 알아볼 차례입니다.
왜냐하면 setter를 사용해서 주입시킬 것이라 생각했었기 때문입니다.

실제로 getter와 setter로 프로퍼티를 가져오는지 확인하기

BeanDeserializer를 생성하기 위한 BeanDeserializerFactory에서 해당 작업을 수행하는 것을 확인할 수 있었습니다.
위의 addBeanProps 메서드를 들어가보면 아래와 같이 Setter와 Getter로 Bean의 프로퍼티를 찾는 것을 알 수 있습니다. (사진에 안보이지만 아래에는 Getter로 찾는 부분이 있습니다.)

그럼 값 주입은 어떻게?

BeanDeserializer에서 DTO에 주입하는 코드를 찾았습니다.
아래 코드에서 p는 JsonParser입니다. propertyName으로 이전에 저장했던 프로퍼티를 가져옵니다. 그리고 이제 JsonParser와 Bean을 deserializeAndSet 메서드로 넘겨 값을 저장해주는 것입니다.

이제 prop.deserializeAndSet 메서드로 들어가봅시다!

여기서 이유를 찾았습니다!! 🎉
_fieldjava.lang.reflect 패키지의 Field 자료형입니다. 결국 reflection을 사용해서 값을 주입해주므로 Setter는 필요하지 않는 것입니다.

여러 상황 테스트

테스트에서는 reflection을 사용해 value값을 보여드리기 위해 dto의 변수들을 모두 public으로 변경했습니다.

  1. Setter & Getter 모두 없는 경우 테스트

    objectMapper가 바인딩하는데 DTO에 setter와 getter가 없으니 찾지 못해 오류가 납니다.

  2. Setter만 있는 경우 테스트

  3. Getter만 있는 경우 테스트

이 테스트들을 통해, Setter와 Getter 모두 없는 경우에는 ObjectMapper가 바인딩하는데 오류가 생기며
Setter, Getter 둘 중 하나만 있으면 된다는 것을 알았습니다.

3번째 글을 쓰면서 내리는 결론

  • @RestController에서 @RequestBody의 바인딩은 Jackson라이브러리의 ObjectMapper가 해준다.
  • ObjectMapper@RequestBody가 Property로 구현되어 있거나 생성을 위임한 경우가 아니라면 기본 생성자로 생성한다.
  • 따라서 두 상황이 아니라면 기본 생성자는 꼭 필요하다. (@NoArgsConstructor가 필요한 이유)
  • ObjectMapper는 Setter와 Getter로 DTO의 필드를 가져온다.
  • 그리고 setter를 사용하는 것이 아니라 reflection을 사용해서 필드 값들을 넣어준다.
  • 따라서 DTO에 사용할 필요가 거의 없는 Setter보다 프로덕션 코드에 사용되거나 테스트에서 필요한 Getter를 넣는게 좋겠다는 것이 저의 의견입니다. (쉽게 말해, Setter 또는 Getter 둘 중 하나만 있으면 되는데 Getter를 넣자!)

오타 또는 잘못된 내용이 있으면 댓글이나 메일 주시면 정말 감사하겠습니다.

참고한 자료

4개의 댓글

comment-user-thumbnail
2021년 6월 23일

많은 도움이 됐습니다~ 글 #2에서 그 다음글이 #3이 아니어서 3의 존재유무를 몰랐습니다! #2 뒤쪽에 #3의 링크를 달면 더 많은 사람들이 볼 수 있을 것 같습니다!

답글 달기
comment-user-thumbnail
2021년 12월 5일

최고예요

답글 달기
comment-user-thumbnail
2022년 8월 12일

사랑해요~

답글 달기
comment-user-thumbnail
2023년 2월 18일

좋은 글 잘봤습니다 감사합니다.

답글 달기