포스트

리플렉션

리플렉션

리플렉션이란?

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 접근 제한자에도 접근이 가능합니다. 단, 캡슐화 원칙을 위반하므로 조심해서 사용해야 합니다.

참고

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.