리플렉션이란?
reflec에서 유래된 것으로, “반사하다” 또는 “되돌아 보다” 라는 의미를 가지고 있습니다.
클래스가 재공하는 다양한 정보를 동적을 분석하고 사용하는 기능을 의미합니다.
리플렉션으로 할 수 있는 것
- 클래스의 메타데이터
- 클래스 이름
- 접근 제어자
- 부모 클래스
- 구현된 인터페이스 등
- 필드 정보
- 필드의 이름
- 타입 접근 제어자를 확인하고, 해당 필드 값을 읽거나 수정
- 메서드 정보
- 메서드 이름
- 반환 타입
- 매개변수 정보를 확인하고, 실행 중에 동적으러 메서드를 호출 할 수 있음
- 생성자 정보
- 생성자의 매개변수 타입과 개수를 확인하고, 동적으로 객체를 생성할 수 있음
클래스 메타데이터 조회
클래스 조회
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| public class BasicV1 {
public static void main(String[] args) throws ClassNotFoundException {
// 클래스 메타데이터 조회 방법 3가지
// 1. 클래스에서 찾기
Class<BasicData> basicDataClass1 = BasicData.class;
System.out.println("basicDataClass1 = " + basicDataClass1);
// 2. 인스턴스에서 찾기
BasicData basicInstance = new BasicData();
Class<? extends BasicData> basicDataClass2 = basicInstance.getClass();
System.out.println("basicDataClass2 = " + basicDataClass2);
// 3. 문자로 찾기
String className = "reflection.data.BasicData"; // 패키지명 주의
Class<?> basicDataClass3 = Class.forName(className);
System.out.println("basicDataClass3 = " + basicDataClass3);
}
}
|
기본 정보
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
| public class BasicV2 {
public static void main(String[] args) {
Class<BasicData> basicData = BasicData.class;
// 패키지 명 포함 클래스 명 조회
System.out.println("basicData.getName() = " + basicData.getName());
// 클래스 명만 조회
System.out.println("basicData.getSimpleName() = " + basicData.getSimpleName());
// 패키지 조회
System.out.println("basicData.getPackage() = " + basicData.getPackage());
// 부모 클래스 조회
System.out.println("basicData.getSuperclass() = " + basicData.getSuperclass());
// 인터페이스 조회
System.out.println("basicData.getInterfaces() = " + Arrays.toString(basicData.getInterfaces()));
// 인터페이스 여부
System.out.println("basicData.isInterface() = " + basicData.isInterface());
// Enum 여부
System.out.println("basicData.isEnum() = " + basicData.isEnum());
// 아노테이션 여부
System.out.println("basicData.isAnnotation() = " + basicData.isAnnotation());
// 접근 제어자 조히
int modifiers = basicData.getModifiers();
System.out.println("basicData.getModifiers() = " + modifiers);
System.out.println("isPublic = " + Modifier.isPublic(modifiers));
System.out.println("Modifier.toString() = " + Modifier.toString(modifiers));
}
}
|
메서드 탐색
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| public class MethodV1 {
public static void main(String[] args) {
Class<BasicData> helloClass = BasicData.class;
// 상속받은 모든 public 메서드
System.out.println("===== methods() =====");
Method[] methods = helloClass.getMethods();
for (Method method : methods) {
System.out.println("method = " + method);
}
// 클래스에 선언한 모든 메서드 (private 포함)
System.out.println("===== declaredMethods() =====");
Method[] declaredMethods = helloClass.getDeclaredMethods();
for (Method method : declaredMethods) {
System.out.println("declaredMethod = " + method);
}
}
}
|
동적 호출
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| public class MethodV2 {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
// 정적 메서드 호출 - 일반적인 메서드 호출
BasicData helloInstance = new BasicData();
helloInstance.call(); // 이 부분은 코드를 변경하지 않는 이상 정적이다.
// 동적 메서드 호출 - 리플렉션 사용
Class<? extends BasicData> helloClass = helloInstance.getClass();
String methodName = "hello";
// 메서드 이름을 변수로 변경할 수 있다.
Method method1 = helloClass.getDeclaredMethod(methodName, String.class);
/// 동적 호출
/// 첫 번째 인자로 인스턴스, 두 번째 인자부터 파라미터에 맞는 값을 넘기면 됩니다.
Object returnValue = method1.invoke(helloInstance, "hi");
System.out.println("returnValue = " + returnValue);
}
}
|
필드 탐색
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| public class FieldV1 {
public static void main(String[] args) {
Class<BasicData> helloClass = BasicData.class;
// 상속 받은 모든 public 필드
System.out.println("====== fields() ======");
Field[] fields = helloClass.getFields();
for (Field field : fields) {
System.out.println("field = " + field);
}
// 클래스에 선언한 모든 필드 (private 포함)
System.out.println("====== declaredFields() ======");
Field[] declaredFields = helloClass.getDeclaredFields();
for (Field field : declaredFields) {
System.out.println("declaredField = " + field);
}
}
}
|
필드 값 변경
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| public class FieldV2 {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
User user = new User("id1", "userA", 20);
System.out.println("기존 이름 = " + user.getName());
Class<? extends User> aClass = user.getClass();
Field nameField = aClass.getDeclaredField("name");
// private 필드에 접근 허용, private 메서드도 이렇게 호출 가능
nameField.setAccessible(true);
nameField.set(user, "userB");
System.out.println("변경된 이름 = " + user.getName());
}
}
|
생성자 탐색
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| public class ConstructV1 {
public static void main(String[] args) throws ClassNotFoundException {
Class<?> aClass = Class.forName("reflection.data.BasicData");
// public 생성자
System.out.println("===== constructors() =====");
Constructor<?>[] constructors = aClass.getConstructors();
for (Constructor<?> constructor : constructors) {
System.out.println(constructor);
}
// 모든 생성자 (private 포함)
System.out.println("===== declaredConstructors() =====");
Constructor<?>[] declaredConstructors = aClass.getDeclaredConstructors();
for (Constructor<?> constructor : declaredConstructors) {
System.out.println(constructor);
}
}
}
|
객체 생성
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| public class ConstructV2 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
Class<?> aClass = Class.forName("reflection.data.BasicData");
Constructor<?> constructor = aClass.getDeclaredConstructor(String.class);
// private 접근 허용
constructor.setAccessible(true);
// 객체 생성
Object instance = constructor.newInstance("hello");
System.out.println("instance = " + instance);
// 해당 인스턴스의 메서드 실행
Method method1 = aClass.getDeclaredMethod("call");
method1.invoke(instance);
}
}
|
xxx.setAccessible()를 이용하면 private 접근 제한자에도 접근이 가능합니다. 단, 캡슐화 원칙을 위반하므로 조심해서 사용해야 합니다.
참고