정의
리플렉션이란 런타임 단계에서 클래스의 정보를 분석해내는 자바 API로 클래스의 메서드, 타입, 필드, 어노테이션 등의 정보를 접근 또는 수정을 할수 있습니다.
즉, 클래스의 정보를 통해 '거울에 비친 상'과 같이 똑같은 형태를 만들고 이를 통해 메서드, 타입, 필드, 어노테이션과 같은 자원에 접근하거나 수정할 수 있는 자바 API를 의미합니다.
Reflection이 왜 필요할까?
Java는 동적으로 객체를 생성해주는 기술이 없습니다.
따라서 JVM의 메모리 영역에 있는 데이터를 런타임에 가져와서 사용하게 해줄 존재가 필요합니다.
특히, 다이나믹 프록시, 프레임워크, 라이브러리 등에서 들어오는 클래스의 정보를 모르기 때문에 이러한 정보를 아는 것을 목적으로 사용하게 됩니다.
사용 예
예를 통해서 리플렉션이 사용되는 방법을 알아보겠습니다.
public class Friend {
private String name;
private Friend() {}
public Friend(String name) {
this.name = name;
}
public void callName() {
System.out.println("내 이름은 " + name + "이야!");
}
public void callAge() {
secretAge();
}
private void secretAge() {
System.out.println("나이가 30이야..");
}
}
나이를 밝히지 않으려는 한 사람이 있다고 합시다.
따라서 외부에서 호출되는 것을 막기 위해 private 접근 제어자를 사용했습니다.
클래스 정보 조회 방법
클래스 정보를 조회하는 방법을 모두 적용해보면
// 1
Class<Friend> classA = Friend.class;
// 2
Friend memberA = new Friend();
Class<? extends Friend> classB = memberA.getClass();
// 3
Class<?> classC = Class.forName("hudi.reflection.Friend");
1번은 class 프로퍼티를 통해 획득하는 방법입니다.
2번은 인스턴스의 getClass() 메서드를 이용하는 방법입니다.
3번은 class의 forName() 정적 메소드에 FQCN(Full Qualified Class Name)을 전달해 해당 클래스의 인스턴스를 얻는 방법입니다.
생성자 조회 방법
public static void main(String[] args) throws NoSuchMethodException, ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException {
// JVM에 있는 클래스 정보 가져오기
Class<?> class1 = Class.forName("org.gyu.test.Friend");
// 리플렉션을 통해 생성자 가져오기
Constructor<?> constructor = class1.getDeclaredConstructor();
Constructor<?> constructor1 = class1.getDeclaredConstructor(String.class);
// 생성자로 객체 생성하기
Object user1 = constructor.newInstance();
Object user2 = constructor1.newInstance("gyu");
위와 같이 reflection을 이용해서 생성자를 가져오고 객체를 생성해주려고합니다.
하지만 user1을 생성할 때, 에러가 발생합니다. 왜냐하면 생성자를 외부에서 생성하는 것을 막기 위해 private으로 접근제어자를 만들어 줬기 때문입니다.
public static void main(String[] args) throws NoSuchMethodException, ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException {
// JVM에 있는 클래스 정보 가져오기
Class<?> class1 = Class.forName("org.gyu.test.Friend");
// 리플렉션을 통해 생성자 가져오기
Constructor<?> constructor = class1.getDeclaredConstructor();
Constructor<?> constructor1 = class1.getDeclaredConstructor(String.class);
constructor.setAccessible(true); // 생성자에 접근할 수 있도록 설정
// 생성자로 객체 생성하기
Object user1 = constructor.newInstance();
Object user2 = constructor1.newInstance("gyu");
따라서 setAccesible(true)를 이용해 private의 접근 제한을 풀어주고 사용하면 생겼던 에러가 없어지는 것을 확인해 보실수 있습니다.
필드 조회 방법
public static void main(String[] args) throws NoSuchMethodException, ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException {
// JVM에 있는 클래스 정보 가져오기
Class<?> class1 = Class.forName("org.gyu.test.Friend");
// 리플렉션을 통해 생성자 가져오기
Constructor<?> constructor = class1.getDeclaredConstructor();
Constructor<?> constructor1 = class1.getDeclaredConstructor(String.class);
constructor.setAccessible(true); // 생성자에 접근할 수 있도록 설정
// 생성자로 객체 생성하기
Object user1 = constructor.newInstance();
Object user2 = constructor1.newInstance("gyu");
for(Field field : class1.getFields()) {
System.out.println(field);
}
}
class에서 filed를 들고 오면 됩니다. 하지만 외부에서 사용하지 못하게 private으로 지정해두었기에 찾을 수 없습니다. 따라서
for(Field field : class1.getDeclaredFields()) {
System.out.println(field);
}
private도 접근 가능한 getDeclaredFields를 사용하면 private도 들고 올수 있는 것을 알수 있습니다.
지정한 이름을 알려면
public static void main(String[] args) throws NoSuchMethodException, ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException {
// JVM에 있는 클래스 정보 가져오기
Class<?> class1 = Class.forName("org.gyu.test.Friend");
// 리플렉션을 통해 생성자 가져오기
Constructor<?> constructor = class1.getDeclaredConstructor();
Constructor<?> constructor1 = class1.getDeclaredConstructor(String.class);
constructor.setAccessible(true); // 생성자에 접근할 수 있도록 설정
// 생성자로 객체 생성하기
Object user1 = constructor.newInstance();
Object user2 = constructor1.newInstance("gyu");
for(Field field : class1.getDeclaredFields()) {
System.out.println(field);
field.setAccessible(true);
System.out.println("name : "+ field.get(user2));
}
setAccesible을 통해 접근제어가 가능하도록 변경하고, get을 통해 가져오면 됩니다.
변경하려면 field.set(user1, "변경하고 싶은 이름"); 을 넣어주면 변경되는것을 알 수 있습니다.
메소드 조회 및 호출
public static void main(String[] args) throws NoSuchMethodException, ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException {
// JVM에 있는 클래스 정보 가져오기
Class<?> class1 = Class.forName("org.gyu.test.Friend");
// 리플렉션을 통해 생성자 가져오기
Constructor<?> constructor = class1.getDeclaredConstructor();
Constructor<?> constructor1 = class1.getDeclaredConstructor(String.class);
constructor.setAccessible(true); // 생성자에 접근할 수 있도록 설정
// 생성자로 객체 생성하기
Object user = constructor1.newInstance("gyu");
Method callName = class1.getDeclaredMethod("callName");
Method callAge = class1.getDeclaredMethod("callAge");
callAge.setAccessible(true);
callName.invoke(user);
callAge.invoke(user);
이제 메소드도 되는지 확인해보겠습니다.
이제 거의 반복적인 작업이죠. method를 getDeclaredMethod를 통해 가져오고,
setAccesible을 통해 접근제어를 풀고
invoke를 해서 가져오면!
나이를 알려주기 싫었던 사람의 나이가 공개됩니다...
너무 강력한 reflection의 기능이라고 할수 있습니다. 이처럼 reflection은 강한 권한을 가진만큼 주의사항이 있습니다.
주의사항
1. 컴파일 에러가 아닌 런타임 시에 에러가 발생하기 때문에 상당한 주의가 필요하며 되도록 사용을 하지 않는 것을 권장합니다.
2. 동적으로 생성하기 때문에 JVM 컴파일러가 최적화 할 수 없습니다. 따라서 일반 메서드 호출에 비해 성능이 떨어집니다.
3. 캡슐화를 파괴합니다. private 접근 제어자에도 접근할수 있게 때문에 주의해야합니다.
참고 : final로 지정한것도 reflection을 이용하면 변경할 수 있습니다.
'기술 스텍 > Java' 카테고리의 다른 글
[Java] 접근 제어자 (2) | 2024.04.21 |
---|---|
[Java] 인터페이스 vs 추상클래스 용도 차이 (3) | 2024.04.21 |
[Java] 컴포지션이란? (60) | 2024.04.12 |
[Java] Interned String (1) | 2024.04.05 |
[Java] Record란? (0) | 2024.04.05 |