public ChatRoomDTO findByUserId(String userId);
처럼 DTO를 쓰려고 하면 에러 뜬다.org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [com.example.chat.entity.ChatRoom] to type [com.example.chat.dto.ChatRoomDTO]
💡 DTO에 기본 생성자가 필요한 이유
우선, 기본 생성자가 필요한 이유를 알기 위해서는
스프링이 어떻게 Dto를 JSON으로 맵핑하는지 그 원리를 알아야 한다.
스프링은 바로 Jackson 라이브러리의 ObjectMapper
를 사용하여 JSON
으로 맵핑한다!
ObjectMapper는 직렬화(serialize), 역직렬화(deserialize)를 수행하는데,
이때 직렬화는 Java Object → JSON
으로 parsing하는 것,
역
직렬화는 JSON → Java Object
로 파싱하는 것을 의미한다.
컨트롤러에서 DTO를 @RequestBody
를 통해 가져올 때, 바인딩을 ObjectMapper가 수행한다.(기존 레거시할때 봤음)
이렇게 ObjectMapper가 직렬화, 역직렬화를 수행하여 맵핑할 때,
DTO를 DTO의 기본 생성자를 이용하여 생성하게 된다.
- 추가적으로, DTO의 필드들은 ObjectMapper의 Setter 또는 Getter로 가져온다!
또한, Setter로 DTO 필드들에 주입을 하는 것이 아니라 reflection이라는 기능을 통해 값을 주입하므로 Setter는 딱히 필요가 없다.
따라서, Getter만 생성하는 것이 좋다!
- ObjectMapper는 직렬화, 역직렬화를 수행한다. (자바 객체를 JSON으로 변환 (json을 사용하는 이유는 JSON이 단지 데이터 포맷임, 그러나 XML보다 적은 용량으로 데이터 전송을 할 수 있고, 가독성이 좋다. ➡︎ 경량의 데이터 교환 형식))
- DTO에
@Setter
쓰지 말자~
➡︎➡︎ DTO에@Builder
넣을 때 파라미터에 값이 전달되는데, 이때@NoArgsConstructor
없으면 안됨! dto에 noArgs 넣어주는 이유임
Reflection이란?
- 구체적인 클래스 타입을 알지 못해도 그 클래스의 메소드, 타입, 변수들에 접근할 수 있도록 해주는 자바 API
- 런타임에 지금 실행되고 있는 클래스를 가져와서 실행해야하는 경우 동적으로 객체를 생성하고 메서드를 호출하는 방법이다.
자바의 리플렉션은 클래스, 인터페이스, 메소드들을 찾을 수 있고, 객체를 생성하거나 변수를 변경하거나 메소드를 호출할 수 있다.- 단점
- 캡슐화를 저해한다.
- 런타임 시점에서 인스턴스를 생성하므로 컴파일 시점에서 해당 타입을 체크할 수 없다.
- 런타임 시점에서 인스턴스를 생성하므로 구체적인 동작 흐름을 파악하기 어렵다.
- 단순히 필드 및 메소드를 접근할 때보다 리플렉션을 사용하여 접근할 때 성능이 느리다. (모든 상황에서 성능이 느리지는 않음.)
➡︎➡︎➡︎ 그럼에도 사용하는 이유?
- 리플렉션 API를 통해 런타임 중, 클래스 정보에 접근하여 클래스를 원하는 대로 조작할 수 있다. 심지어
private
접근 제어자로 선언한 필드나 메소드까지 조작이 가능하다. 객체 지향 설계에서 중요한 캡슐화가 깨지므로 사용하면 안 될 기술처럼 보인다.- 규모가 작은 콘솔 단계에서는 개발자가 충분히 컴파일 시점에 프로그램에서 사용될 객체와 의존 관계를 모두 파악할 수 있다.
하지만 프레임워크와 같이 큰 규모의 개발 단계에서는 수많은 객체와 의존 관계를 파악하기 어렵다. 이때 리플렉션을 사용하면 동적으로 클래스를 만들어서 의존 관계를 맺어줄 수 있다.- 가령, Spring의 Bean Factory를 보면,
@Controller
,@Service
,@Repository
등의 어노테이션만 붙이면 Bean Factory에서 알아서 해당 어노테이션이 붙은 클래스를 생성하고 관리해 주는 것을 알 수 있다.
개발자는 Bean Factory에 해당 클래스를 알려준 적이 없는데, 이것이 가능한 이유는 바로 리플렉션 덕분이다.
런타임에 해당 어노테이션이 붙은 클래스를 탐색하고 발견한다면, 리플렉션을 통해 해당 클래스의 인스턴스를 생성하고 필요한 필드를 주입하여 Bean Factory에 저장하는 식으로 사용이 된다.
물론, 위에서 말했듯 캡슐화를 저해하므로 꼭 필요한 상황에서만 사용하는 것이 좋다.