오늘 N:N 테이블 사이에서 DTO변환을 하다가 다음과 같은 오류를 만났다.
cannot deserialize from Object value (no delegate- or property-based Creator)
처음엔 이해가 되지 않았다. 분명히 DTO변환에 필요한 생성자(파라미터가 들어가는) 만들어 두었고, mapper를 이용한 변환에도 특별히 문제가 될만한 설계상의 실수는 존재하지 않았다. 그렇게 몇분간 방법을 찾아 헤매다가,
이 문제가 Jackson 라이브러리는 기본 생성자가 존재하지 않으면 객체를 생성할 수 없다는 사실을 상기하게 되었다.
Jackson 라이브러리가 객체를 생성할 때, Java Reflection API를 사용하게 된다. 이는 Spring Framework를 공부하면서 이따끔씩 만나게 된 용어인데, 이 API를 사용하는 대표적인 예시로는 Spring의 Bean Factory, Jackson library, intellij를 들 수 있겠다. Reflection API에 대한 예시를 통한 이해는 https://tecoble.techcourse.co.kr/post/2020-07-16-reflection-api/ 이 글을 참고하도록 하고 '하루회고' 포스트에서는 그래서 왜 ?
- 우리가 기본생성자를 열어두어야하는지
- 어떤 이점이 있을 수 있는가
- 어떠한 문제가 생길 수 있는지 생각해보자
1. 왜 기본 생성자를 열어 두어야 하는가?
왜 기본 생성자가 필요한 것인가에 대해 찾아보다가, 이런 문장을 발견하였다
It's not mandatory to define default constructor, but if you are writing Hibernate persistent class, JPA entities, or using the Spring framework to manage object creation and wiring dependencies, you need to be a bit careful. Many of open source framework, uses reflection to create instance or Object at runtime, based upon the name of the class.
기본생성자가 꼭 필요한 것은 아니지만, 하이버네이트 영속성 클래스, JPA 엔티티와 같이 지금 프로젝트에 반드시 사용하는 프레임워크 안에서는 객체는 생성하고 의존성 주입에 있어서 Reflection을 사용하기 때문에 필요하다는 이야기다.
그래서 리플렉션은 무엇인가를 다시한번 찾아보았다.
리플렉션(Reflection)은 Java에서 실행중인 프로그램의 구조를 분석하거나 조작할 수 있는 기능이다. 쉽게말하면 클래스, 메서드, 필터등에 접근할 수 있으며, 동적으로 객체를 생성하고 메서드를 호출할 수 있게 해준다. 이 때 Java Reflection이 가져올 수 없는 정보 중 하나가 바로 생성자의 인자 정보들이다. 따라서 기본 생성자 없이 파라미터가 있는 생성자만 존재한다면 java Reflection이 객체를 생성할 수 없게 되는 것이다.
아 그렇다면, Java Reflection의 기능을 사용하는 프레임워크 or 라이브러리는 "항상 기본생성자를 열어 두도록 하자! "라고 결론 지을 수 있었다.
2. 어떤 이점이 있을 수 있는가?
첫째로, 리플렉션 API를 사용하는 Jackson 라이브러리를 통해 JSON 데이터를 Java객체로 동적으로 생성하고 필요한 값들을 할당할 수 있게된다. (오늘 생겼던 문제)
둘째, Spring을 시작하면서 가장 먼저 배웠던 Bean과 Bean Factory도 Reflection API와 관계가 깊다. 일반적으로 Spring 애플리케이션의 시작 시점에 Bean Factory는 설정된 빈들을 생성하고 의존성 주입을 수행한다. 이 과정에서 리플렉션을 사용하여 동적으로 객체를 생성하고, 필드를 설정하거나 메소드를 호출하는 등의 작업을 수행한다.
세번째, 우리가 개발하는 동안 가장 많이 사용하는 인텔리제이(IntelliJ)의 자동 완성 기능은 리플렉션을 사용하여 사용자가 작성한 클래스의 메서드나 필드 목록을 가져와 자동 완성 목록에 표시한다
결론적으로, 리플렉션에 기반한 프레임워크와 라이브러리에서 많이 사용되며, 클래스에 대한 정보를 런타임에 얻고 동적으로 처리하기 위해 사용되어 개발을 좀 더 쉽게 해줄 수 있게 해준다.
3. 어떠한 문제가 생길 수 있는지 생각해보자
개발자가 편해지면서 얻게되는 기술들은 적절히 사용하는 것을 기본 원칙으로 하되, 이 기술을 사용했을 때 나타날 수 있는 문제에 대해서도 한번은 짚고 넘어갈 필요가 있다.
이런 Reflection 의 가장 치명적인 단점으로는 성능 오버헤드가 크다고 한다. 컴파일 타임이 아닌 런타임에 동적으로 타입을 분석하고 정보를 가져오므로 JVM을 최적화할 수 없기 때문이다. 뿐만 아니라 직접 접근할 수 없는 private 인스턴스 변수, 메서드에 접근하기 때문에 내부를 노출하면서 추상화가 깨진다. 이로 인해 예기치 못한 부작용이 발생할 수 있다.
그래서 결론은?
"Java Reflection API 는 개발자가 런타임환경에서 구체적이지 않은 객체를 해결해주는 아주 좋은 API구나. 기본생성자는 열어두도록하지만, 오버헤드로 인한 문제는 기억해두도록 하자" 로 결론 지을 수 있을것 같다.