리플렉션 (Reflection)
리플렉션은 class, constructor, field, method 등을 다루기 위한 java표준 API이다. 클래스나 메소드의 메타정보를 동적으로 획득하고, 코드도 동적으로 호출할 수 있다.
다음과 같은 클래스가 있다.
패키지 | 클래스 |
java.lang | Class |
java.lang.reflect | Constructor |
java.lang.reflect | Field |
java.lang.reflect | Method |
Spring 등의 프레임워크는 많은 내부처리에서 리플렉션을 이용하고 있다. 따라서 리플렉션을 이해하면 다음과 같은 장점이 있다.
・프레임워크의 소스 코드를 이해할 수 있다.
・소스 코드를 읽지않고도 프레임워크 내부의 처리를 이미지화 할 수 있다.
・스스로 프레임워크를 만들수 있다.
다만, 보통 실무의 업무로직등에서는 리플렉션기술을 사용하는 것을 권하지 않는다. 리플렉션으로 클래스와 메소드의 메타정보를 사용해서 어플리케이션을 동적으로 유연하게 만들 수 있지만, 리플렉션은 런타임에 동작하기 때문에 컴파일 시점에 오류를 잡을 수 없다.
따라서 리플렉션은 일반적으로 사용하는 것을 권장하지 않는다. 컴파일시점에 오류를 잡도록 해야 더 안전하다. 즉, 프레임워크 개발 또는 매우 일반적인 공통 처리가 필요할 때 부분적으로 사용해야 한다.
리플렉션 기본
1. Class 클래스
Class 는 클래스를 나타내는 클래스이다. 리플렉션의 중심적인 존재이다.
Class 클래스의 정의는 Class<T> 같이 제네릭이 붙어있다. T 는 클래스를 지정한다.
전체 클래스에는 static 을 암묵적으로 작성하고있다. 그 클래스를 지칭하는 Class 인스턴스가 대입되어있다.
public class Member {
public static final Class<Member> class = (Member 를 지칭하는 Class 인스턴스);
}
여기에 더해 모든 클래스에는 getClass() 라는 메소드가 정의되어 있다. (java.lang.Object 클래스에 정의되어 있다.)
이 메소드의 반환값은 .class 와 같은 '그 클래스를 지칭하는 Class 인스턴스'이다.
Member member = new Member();
Class<? extends Member> memberClass = member.getClass();
2. 클래스명 취득
Class 클래스에는 클래스명을 취득하기위한 메소드가 있다.
메소드명 | 설명 |
getName() | FQCN (Fully Qualified Class Name) 이 반환된다. |
getSimpleName() | 단순한 클래스명이 반환된다. |
예제 코드
package hello.proxy.reflection;
import org.junit.jupiter.api.Test;
public class ReflectionTest {
public class Member {
}
@Test
void nameTest() {
Class<?> objectClass = Object.class;
String fqcn = objectClass.getName();
System.out.println("getName() = " + fqcn);
String simpleName = objectClass.getSimpleName();
System.out.println("getSimpleName() = " + simpleName);
Class<Member> memberClass = Member.class;
String memberFqcn = memberClass.getName();
System.out.println("getName() = " + memberFqcn);
String memberSimpleName = memberClass.getSimpleName();
System.out.println("getSimpleName() = " + memberSimpleName);
}
}
출력결과
getName() = java.lang.Object
getSimpleName() = Object
getName() = hello.proxy.reflection.ReflectionTest$Member
getSimpleName() = Member
3. 리플렉션으로 인스턴스 생성
예제코드
Team.java
public class Team {
public String name; // 학습위해 public
private int number;
public Team(String name, int number) {
this.name = name;
this.number = number;
}
public int transferNumber(int value) {
return number + value;
}
@Override
public String toString() {
return "Team{" +
"name='" + name + '\'' +
", number=" + number +
'}';
}
}
ReflectionTest.java
public class ReflectionTest {
@Test
void instanceTest() {
try {
// Team 클래스를 지칭하는 Class 인스턴스를 생성
Class<?> teamClass = Class.forName("hello.proxy.reflection.Team");
// teamClass 으로 생성자 취득 : getConstructor()
Constructor<?> constructor = teamClass.getConstructor(String.class, int.class);
// 생성자로 인스턴스 생성
Object teamInstance = constructor.newInstance("test name", 2);
// 인스턴스 생성 확인
System.out.println(teamInstance);
} catch (Exception e) {
e.printStackTrace();
}
}
}
출력확인
Team{name='test name', number=2}
name, number 로 설정한 test name, 2 로 생성된 Team 인스턴스를 확인할 수 있다.
4. 리플렉션으로 필드 사용
Class 클래스에는 필드를 취득하는 메소드가 있다.
메소드명 | public 필드 취득 | public 이외 필드 취득 | 슈퍼클래스에 정의된 필드 취득 |
getField() | ⭕ | ✖️ | ⭕ |
getDeclaredField() | ⭕ | ⭕ | ✖️ |
위 예제코드의 Team.java 와 ReflectionTest.java 를 이어서 이용한다.
public class ReflectionTest {
@Test
void fieldTest() {
try {
// Team 클래스를 지칭하는 Class 인스턴스를 생성
Class<?> teamClass = Class.forName("hello.proxy.reflection.Team");
System.out.println("===== getFields() : public Field 만 취득 =====");
Arrays.stream(teamClass.getFields()).forEach(System.out::println);
System.out.println("===== getDeclaredFields() : 모든 Field 취득 =====");
Arrays.stream(teamClass.getDeclaredFields()).forEach(System.out::println);
System.out.println();
// teamClass 으로 생성자 취득 : getConstructor()
Constructor<?> constructor = teamClass.getConstructor(String.class, int.class);
// 생성자로 인스턴스 생성
Object teamInstance = constructor.newInstance("test name", 2);
// public 필드 : number 취득
Field numberField = teamClass.getDeclaredField("number");
numberField.set(teamInstance, 999);
System.out.println("===== public field 값 변경 확인 =====");
System.out.println("number 변경 -> " + teamInstance);
// private 필드 : name 취득
Field nameField = teamClass.getDeclaredField("name");
// private 값에 set 접근 하면 java.lang.IllegalAccessException 발생
// setAccessible(true) 를 설정하면 private 필드도 값을 취득 및 설정할 수 있다.
nameField.setAccessible(true);
// name 필드 값을 취득
Object name = nameField.get(teamInstance);
nameField.set(teamInstance, "이름 변경");
System.out.println("===== private field 값 변경 확인 =====");
System.out.println("이름 변경 -> " + teamInstance);
} catch (Exception e) {
e.printStackTrace();
}
}
}
출력확인
===== getFields() : public Field 만 취득 =====
public int hello.proxy.reflection.Team.number
===== getDeclaredFields() : 모든 Field 취득 =====
private java.lang.String hello.proxy.reflection.Team.name
public int hello.proxy.reflection.Team.number
===== public field 값 변경 확인 =====
number 변경 -> Team{name='test name', number=999}
===== private field 값 변경 확인 =====
이름 변경 -> Team{name='이름 변경', number=999}
5. 리플렉션으로 메소드 사용
메소드명 | public 필드 취득 | public 이외 필드 취득 | 슈퍼클래스에 정의된 필드 취득 |
getMethod() | ⭕ | ✖️ | ⭕ |
getDeclaredMethod() | ⭕ | ⭕ | ✖️ |
위 예제코드의 Team.java 를 이어서 이용한다.
public class ReflectionTest {
@Test
void methodTest() {
try {
System.out.println("===== 메소드 목록 =====");
Arrays.stream(Team.class.getDeclaredMethods()).forEach(System.out::println);
// Team 클래스의 transferNumber 메소드 취득
Method numberMethod = Team.class.getMethod("transferNumber", int.class);
// Team 데이터
Team team = new Team("빌리", 10);
System.out.println("===== 메소드 실행 전 =====");
System.out.println(team);
// transferNumber 메소드 실행 : 10 + 5 = 15 리턴
Object obj = numberMethod.invoke(team, 5);
System.out.println("===== invoke() 메소드 실행 후 =====");
System.out.println("number = " + obj);
} catch (Exception e) {
e.printStackTrace();
}
}
}
출력확인
===== 메소드 목록 =====
public java.lang.String hello.proxy.reflection.Team.toString()
public int hello.proxy.reflection.Team.transferNumber(int)
===== 메소드 실행 전 =====
Team{name='빌리', number=10}
===== invoke() 메소드 실행 후 =====
number = 15
6. 이외에도 가능한 것
- 상위클래스 취득 : getSuperclass()
- 생성자 취득: getConstructor(), getConstructors(), getDeclaredConstructor(), getDeclaredConstructors()
- 인터페이스 목록 취득 : getInterfaces()