Reflection이란?
•
private 접근 제어자가 있는 메소드에 접근할 때는 setAccessible() 의 인자를 true로 설정
로드된 class라 함은, JVM의 클래스 로더에서 클래스 파일에 대한 로딩을 완료한 후, 해당 클래스의 정보를 담은 Class 타입의 객체를 생성하여 메모리의 힙 영역에 저장해 둔 것을 의미한다.
new 키워드를 통해 만드는 인스턴스와는 다른 것이다!
•
invoke() 메소드를 통해 리플렉션 API로 얻어 온 메소드를 호출할 수 있다.
사용 방법
Reflection 사용에 앞서, 힙 영역에 로드된 Class 타입의 객체를 가져와야 한다.
1) 클래스.class로 가져오기
2) 인스턴스.getClass()로 가져오기
3) Class.forName(”클래스명”)으로 가져오기
장점 및 단점
[ 장점 ]
•
런타임 시점에서 클래스의 인스턴스를 생성하고, 접근 제어자와 관계 없이 필드와
메소드에 접근하여 필요한 작업을 수행할 수 있는 유연성을 가지고 있다.
[ 단점 ]
•
캡슐화를 저해한다.
•
런타임 시점에서 인스턴스를 생성하므로 컴파일 시점에서 타입을 체크할 수 없다.
•
런타임 시점에서 인스턴스를 생성하므로 구체적인 동작 흐름을 파악하기 어렵다.
•
단순히 필드 및 메소드를 접근할 때 보다 리플렉션을 사용하여 접근할 때 성능이 느리다.
사용하는 이유?
Reflection API를 통해 런타임 중, 클래스 정보에 접근하여 클래스를 원하는 대로 조작할 수 있다.
심지어 private 접근 제어자로 선언한 필드나 메소드까지 조작이 가능하다.
객체 지향 설계에서 중요한 캡슐화가 깨지므로 사용하면 안 될 기술처럼 보인다.
규모가 작은 콘솔 단계에서는 개발자가 충분히 컴파일 시점에 프로그램에서 사용될 객체와 의존관계를 모두 파악할 수 있다.
하지만 프레임워크와 같이 큰 규모의 개발 단계에서는 수많은 객체와 의존관계를 파악하기 어렵다.
이때 리플렉션을 사용하면 동적으로 클래스를 만들어서 의존관계를 맺어줄 수 있다.
가령 Spring의 BeanFactory를 보면 @Controller, @Service, @Repositiory 등의 어노테이션만 붙이면 BeanFactory에서 알아서 해당 어노테이션이 붙은 클래스를 생성하고 관리해 주는 것을 알 수 있다.
비록 개발자가 BeanFactory에 해당 클래스를 알려준 적이 없지만 Reflection 덕분에 가능하다.
Spring & Reflection
Spring에서 클래스를 만들 때 기본생성자를 만들어야 하는 이유가 바로 이 Reflection을 사용하기 위함이다.
Almost all frameworks require a default(no-argument) constructor in your class because these frameworks use reflection to create objects by invoking the default constructor
@RequestBody 가 Reflection을 쓰는 예시 중 하나이다.
1.
요청이 들어오면 Spring은 Http body에 있는 JSON 데이터를 DTO로 바인딩해준다.
2.
바인딩 할 DTO의 기본 생성자가 존재하지 않는다면 정상적으로 바인딩이 되지 않는 예외가 발생한다.
3.
이는 스프링의@RequestBody 바인딩 방식이 기본 생성자를 통해 객체를 생성한 후 Java Reflection을 이용해 필드 값을 집어넣어 주는 방식이기 때문이다.
4.
Reflection은 클래스 이름만 알면 생성자, 필드, 메서드 등 클래스의 모든 정보에 접근이 가능하지만, 가져올 수 없는 정보 중 하나가 바로 생성자의 인자(매개변수) 정보들이다.
5.
따라서 Reflection으로 생성할 객체에 모든 필드를 받는 생성자가 있더라도 Reflection은 해당 생성자를 호출할 수가 없다.
6.
그래서 Reflection은 기본 생성자로 객체를 생성하고 필드 값을 강제로 매핑해주는 방식을 사용한다.
JPA & Reflection
JPA에서는 Lazy loading 이라는 개념이 있는데, 이 개념을 사용하면 엔티티에 프록시 객체를 초기화 해준다.
엔티티의 프록시는 원본 엔티티를 상속해서 만든다.
그런데 여기서 부모의 기본 생성자가 private라면?
해당 엔티티를 상속한 프록시를 만들 수 없다.
상속한 객체의 생성자는 반드시 부모 객체의 생성자 super를 호출해야 하는데, private이면 상속받
은 클래스에서 호출할 수 없다.
때문에 엔티티 클래스의 생성자는 private일 수 없는 것이다.
이 에러는 컴파일 타임에 잡아내는 오류가 아니다. 기본 생성자와 접근 제어자에 관련된 예외는 런타임 예외다.
즉시 로딩을 사용하거나 하여 프록시를 사용할 일이 없다면 관련 예외가 발생하지 않고 정상적으로 동작한다.