리플렉션의 정의 : 영어 뜻으로는 투영, 반사라는 의미를 갖고 있다.
프로그램에서의 리플렉션이란?
객체를 통해 클래스의 정보를 분석해 내는 프로그램 기법을 말한다.
리플렉션을 이해하기 위해서 바인딩에 대해 살펴보자.자바에서의 바인딩
자바의 경우 정적바인딩과 동적바인딩을 모두 제공한다.
(정확하게는 동적바인딩을 리플렉션을 이용해 제공한다고 볼 수 있다)class MainClass{ public static void main(String[] args){ Person student = new Student(); student.show(); } } class Person{ void show(){ System.out.println("Person"); } } class Student extends Person{ void show(){ System.out.println("Student"); } }
예제 코드를 살펴보면, student 객체 생성시 컴파일 시점에서는 student는 Person 객체로 바인딩 된다.(이는 정적바인딩이라 한다.)
하지만 실제 실행시에는 student는 Student 객체로 바인딩 되기 때문에 해당 코드의 실행 결과는 Student가 된다.(이를 동적 바인딩이라 한다.)
자바에서는 해당 코드를 오버라이드라고 부른다.
자바에서는 동적바인딩을 사용해 오버라이딩 된 메서드가 항상 실행되도록 보장한다.바인딩과 리플렉션의 관계?
리플렉션의 정의에서 나온 객체를 통해 구체적인 클래스 타입을 분석한다는 의미는 곧 바인딩과 연관되어 있다고 생각한다.
왜냐하면 객체가 바인딩 되기 위해서는 구체적인 클래스 타입을 알아야 하기 때문이다.
그래서 런타임 시점에서 클래스 타입을 동적으로 바인딩 할 수 있는 기술을 리플렉션이라고 할 수 있다.
아래는 리플렉션을 사용하는 예시코드이다.
import java.lang.reflect.Method; public class DumpMethods { public static void main(String args[]) { try { Class c = Class.forName(args[0]); Method m[] = c.getDeclaredMethods(); for (int i = 0; i < m.length; i++) System.out.println(m[i].toString()); }catch (Throwable e) { System.err.println(e); } } }
해당 코드를 살펴보기 전에 리플렉션의 동작과정을 알아보자
리플렉션의 동작과정
클래스로더에 의해 로딩된 자바코드는 컴파일시에 바이트코드로 변경되어 메모리에 저장된다.
클래스 정보는 컴파일되어 네이티브영역인 메타스페이스에 저장된다.
리플렉션을 사용하면 런타임시에 위 영역에 접근하여 클래스의 정보, 접근제어자, 패키지, 어노테이션 등등을 알아낼수 있는 것이다.
- 메타스페이스에 대한 정보 아래 링크 참고
(아직 작성 못함)예시 코드 살펴보기
위 예시 코드는 리플렉션의 패키지(java.lang.reflect)안에 Method클래스를 사용하는 예제이다.(Method클래스 이외에 여러 클래스들이 존재한다.)
리플렉션 패키지 : https://docs.oracle.com/javase/7/docs/api/
Method클래스와 같은 리플렉션 클래스를 사용하기 위해서는 2단계가 필요하다.
첫번째는 알아볼 클래스의 Class객체를 얻어야한다.(해당 과정을 위해 java.lang.Class를 사용한다.)Class c = Class.forName(args[0]); // 해당 코드를 통해 args[0]에 대한 클래스 정보를 얻는다. Method m[] = c.getDeclaredMethods(); // 클래스 안에 정의되어 있는 모든 메서드들을 가져온다.
forname함수의 경우도 리플렉션을 이용한 함수이다.
아래는 forname의 정의부분이다.
caller라는 객체를 리플렉션을 이용해 초기화 시키고 있는 것을 알 수 있다.
두번째는 리플렉션 API를 사용한다.System.out.println(m[i].toString()); // 리플렉션 API 중 하나인 toString()함수를 사용함 // toString 이외에 invoke(함수를 실행시키는 메서드), 등등이 존재한다.
성능 오버헤드가 있다.
리플렉션 기술은 런타임에 사용되는 기술이다. 그래서 jvm 최적화를 할 수 없기 때문에 성능 오버헤드가 발생하게 된다.
그렇기 때문에 애플리케이션 개발 보다는 프레임워크나 라이브러리에서 사용되는 편이다.
리플렉션이 대표적으로 활용되는 곳은 spring의 컴포넌트 스캔과 Spring Data JPA이다.
어떻게 리플렉션 기술이 사용되는 것일까?엔티티에 기본생성자가 필요한 이유
JPA를 사용하기 위해서 엔티티에 기본 생성자가 반드시 있어야 한다는 사실을 알고 있다. 그 이유는 동적으로 객체를 생성 시 리플렉션 기술을 사용하기 때문이다.
리플렉션 API의 경우 생성자의 인자 정보를 가져올 수 없다.(그런 함수는 존재하지 않는다.)
그래서 기본생성자가 있어야 객체를 생성할 수 있다.
왜냐하면 동적으로 객체를 생성하려면 new 객체(인자들,..) 이기 때문에, 인자 정보를 모르면 생성이 불가능하여 기본 생성자가 필요한것이다.
그렇기 때문에 JPA는 DB 값을 객체 필드에 주입할 때 기본 생성자로 객체를 생성한 후 Reflection을 사용하여 값을 매핑하게 된다.컴포넌트 스캔
스프링 컨테이너에서 빈은 빈정의(BeanDefinition) 객체들로 정의해 두고 객체를 생성한다.
빈을 생성힐 때, 빈정의(BeanDefinition)에 따라 객체 생성에 대한 정보를 참조하는데, 이때, 사용되는 것이 리플렉션이다. 즉, 리플렉션을 이용해 객체(빈)를 생성한다.
빈 생성 과정을 살펴보자
우선 스프링 컨테이너가 생성 및 초기화가 된다.
이후 컴포넌트 스캔을 통해 base-package에서 컴포넌트들을 찾아 빈으로 등록해준다.
위의 이미지는 실제 빈을 생성하는 함수인 instantiateClass의 정의부이다.(org.springframework.beans.BeanUtil 패키지에 있다.)
instantiateClass함수는 Constructor객체를 매개변수로 받는데, Constructor객체가 바로 리플렉션 객체이다.
또한 ReflectionUtils의 makeAccessible 함수와 reflect패키지의 Constructor의 newInstance함수,getParameterType함수 등등의 리플렉션 API를 사용하는 것을 볼 수 있다.
(API문서 : https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/util/ReflectionUtils.html)이외에 여러가지로 리플렉션 기술은 사용되고 있다.
※ BeanDefinition을 빈 설정 메타정보라고 부른다.
※ basePackages란 scan이 일어나는 시작 위치입니다.
출처 :
https://taes-k.github.io/2021/05/23/spring-di-reflection/
https://tecoble.techcourse.co.kr/post/2020-07-16-reflection-api/
https://taes-k.github.io/2021/05/23/spring-di-reflection/