런타임에 클래스와 인터페이스 등을 검사하거나, 조작하는 기능이다. JVM
은 개발자가 작성한 코드를 기반으로, JVM
메모리 영역에 클래스, 인터페이스, 메서드 등의 바이트 정보를 보관한다. 이를 통해 실제 클래스로부터 투영된 정보 Reflection
를 바탕으로 런타임 환경에서 의도된 제어를 진행할 수 있다.
주로 프레임워크나 개발도구의 주요 기능 설계들이 대부분 Reflection
이 적용되었다.
Spring
JPA, Hibernates
JUnit, Mockito
Jackson, GSON
등의 JSON Serialization
Intellij
의 자동완성 기능Annotation
설계 및 적용Reflection
을 활용하여 Class
정보를 불러오는 방법은 다음과 같다.
public static void main(String[] args) throws Exception {
// {클래스 타입}.class
Class car = Car.class;
// Class.forName({전체 도메인 이름})
Class car = Class.forName("com.reflection.test.Car");
// class.getName() -> com.reflection.test.Car
// {인스턴스}.getClass();
Car c = new Car();
Class realCar = c.getCalss();
}
getXXX()
: 상위 클래스와 상위 인테페이스에서 상속한 정보를 포함하여 접근 제어자가 public
으로 설정된 정보들을 모두 가져온다.ex )
getMethods(), getContructor(), getField ...
getDeclaredXXX()
: 상속한 정보를 제외하고 직접 클래스에서 설정한 정보들을 접근 제어자에 관계없이 모두 가져온다.ex )
getDeclaredMethods(), getDeclaredContructor(), getDeclaredField ...
getContructor(), getDeclaredConstructor()
를 통해 Constructor
타입의 객체로 생성자를 불러올 수 있다. Constructor
객체는, newInstance()
와 같이 생성자 정보를 기반으로 인스턴스를 생성하는 것이 가능하다.
public static void main(String[] args) throws Exception {
Class car = Class.forName("com.reflection.test.Car");
// 기본 생성자 가져오기
Constructor constructor = car.getDeclaredConstructor();
// String 인자를 받는 생성자 가져오기
Constructor constructor = car.getDeclaredConstructor(String.class);
// 모든 생성자 가져오기
Constructor constructors[] = car.getDeclaredConstructors();
// public 생성자만 가져오기
Constructor constructors[] = car.getConstructors();
// 생성자를 이용한 인스턴스 생성
Car realCar = constructor.newInstance();
}
더불어 접근제어자가 public
이 아닌 경우는, setAccessable(true)
를 통해, 해당 객체 정보에 접근할 수 있다.
// private으로 설정된 생성자 가져오기
Constructor constructor = car.getDeclaredConstructor();
// Object obj = constructor.newInstance(); // java.lang.illegalAccessException
constructor.setAccessible(true);
Object obj = constructor.newInstance(); // ok!
클래스의 필드 정보에 대하여, Field
타입 객체로 불러올 수 있다. 이를 통해 필드의 접근 제어자, 타입, 이름, 값 등을 조회할 수 있다. Field
타입 객체는 get(), set()
함수를 가지고 있어서, 해당 필드가 가지고 있는 값을 조회하거나, 새롭게 변경할 수 있다.
Class car = Class.forName("com.reflection.test.Car");
// name으로 시작하는 필드 불러오기
// Field field = car.getDeclaredField("name");
// car, car의 상속 객체를 포함하여 이름이 name인 field를 가져오기
Field field = car.getField("name");
// car 객체에 선언된 모든 field 가져오기
Field[] fields = car.getDeclaredFields();
만약 필드의 접근제어자가 private
인 경우에는 getDeclaredXXX()
와 setAccessible(true)
를 설정해줘야한다.
Class class = Class.forName("com.reflection.test.Car");
Constructor constructor = class.getConstructor()
Car car = constructor.newInstance()
Field field = car.getField("name");
field.set(car, "아반떼");
field = car.getDeclaredField("name");
field.setAccessible(true);
field.set(car, "아반떼");
getMethod(), getDeclaredMethod()
를 활용하여 클래스의 메서드 정보를 Method
라는 객체로 불러올 수 있다. 이를 통해 메서드의 접근 제어자, 리턴 타입, 이름, 인자 정보, 인자 타입 등의 정보를 가져올 수 있다. Method
객체는 invoke()
함수를 가지고 있어, 해당 메서드를 실행하는 것도 가능하다.
Class car = Class.forName("com.reflection.test.Car");
Class realCar = car.newInstance();
// 인자가 없는 method 가져오기
Method method = car.getMethod("move");
method.invoke(realCar, /*인자*/);
// String 인자를 가진 method 가져오기
method = car.getDeclaredMethod("move", String.class);
method.setAccessible(true);
method.invoke(realCar, "XXX");
라이브러리, 프레임워크 개발의 경우 사용자가 어떤 클래스를 사용하는지 파악할 수 없다. 이를 Class
, Constructor
, Field
, Method
등의 인터페이스 타입으로 추상화하여 적용시킬 수 있기 때문에, 개발이 용이해지는 장점이 있다.
JVM
의 코드 최적화에서 제외가 되어, 일반 메서드 대비 성능이 좋지 않다.Java Reflection
은 라이브리러, 프레임워크를 개발하기에 매우 좋은 수단이며, 실제 Java
진영에서의 대표적인 라이브러리의 주요 기능 모두, Java Reflection
을 통해 개발이 되었다.
그러나, Java Reflection
을 사용하며 나타나는 단점도 많기 때문에, 일반적인 개발의 경우라면 사용을 지양해야하며, 프레임워크, SDK 개발에서 꼭 필요한 경우에만 Java Reflection
을 사용하자.
참고
[10분 테코톡] 헙크의 자바 Refletion
[10분 테코톡] 파랑, 아키의 리플렉션
Reflection은 무엇이고 언제/어떻게 사용하는 것이 좋을까?