자바 리플렉션[Reflection] 이란??

2021. 7. 14. 17:16JAVA

리플렉션(Reflection)이란??

리플렉션은 구체적인 클래스 타입을 알지 못해서 그 클래스의 메소드와 타입 그리고 변수들을 접근할 수 있도록 해주는 자바 API입니다.

  • 과연 내가 작성한 코드인데 모르는게 말이 되나? 라는 의문이 생길지도 모른다...
    하지만 가끔 어떤 타입의 클래스나 변수 혹은 메서드를 사용할지 모르는 경우가 생깁니다.

클래스 정보 조회

  • 리플렉션의 시작은 Class<T>

  • Class 에 접근하는 방법

    • 모든 클래스를 로딩 한 다음 Class<T> 의 인스턴스가 생긴다. "타입.class"로 접근할 수 있다.
    • 모든 인스턴스는 getClass() 메서드를 가지고 있다. "인스턴스.getClass()"로 접근할 수 있다.
    • 클래스를 문자열로 읽어오는 방법
      • Class.forName("~")
      • 클래스패스에 해당 클래스가 없다면 ClassNotFoundException 이 발생한다.
  • Class<T> 를 통해 할 수 있는 것

    • 필드 (목록) 가져오기
    • 메소드 (목록) 가져오기
    • 상위 클래스 가져오기
    • 인터페이스 (목록) 가져오기
    • 에너테이션 가져오기
    • 생성자 가져오기
  • (예시)

    Class<?> bookClass = Class.forName("com.ims.owen.javathecode.Book");
    //        Class<Book> bookClass = Book.class;
            Constructor<?> constructors = bookClass.getConstructor(String.class);
            Book book = null;
            try {
                book = (Book) constructors.newInstance("MyBook");
                System.out.println(book);
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }

애노테이션과 리플렉션

중요 애너테이션

  • @Retention : 해당 애너테이션을 언제까지 유지할 것인가? 소스, 클래스, 런타임
  • @Inherit : 해당 애너테이션을 하위 클래스까지 전달할 것인가?
  • @Target : 어디에 사용할 수 있는가?

리플렉션

  • getAnnotations() : 상속받은 (@Inherit) 애노테이션까지 조회
  • getDeclaredAnnotations() : 자기 자신에만 붙어있는 애너테이션 조회

[예시]

- 클래스
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.FIELD})
@Inherited // 상속이 되는 어노테이션
public @interface MyAnnotation {

    String name() default "Owen";
    int value() default 0;
}
- 실행코드
Annotation[] declaredAnnotations = bookClass.getDeclaredAnnotations();
Arrays.stream(declaredAnnotations).forEach(annotation -> System.out.println(annotation));

클래스 정보 수정 또는 실행

Class 인스턴스 만들기

  • Class.newInstance()는 deprecated 됐으며 이제부터는 생성자 를 통해서 만들어야한다.

생성자로 인스턴스 만들기

  • Constructor.newInstance(params)

필드 값 접근하기/설정하기

  • 특정 인스턴스가 가지고 있는 값을 가져오는 것이기 때문에 인스턴스가 필요하다.
  • Filed.set(Object, value)
  • static 필드를 가져올 때는 object가 없어도 되니깐 null을 넘기면 된다.
    → Why?? : static 필드는 Runtime시 이미 올라가기 때문에 object가 필요없다.

메소드 실행하기

  • Object Method.invoke(object, params)

[예시]

Constructor<?> constructors = bookClass.getConstructor(String.class);
Book book = null;
try {
    book = (Book) constructors.newInstance("MyBook");
    System.out.println(book);
} catch (InstantiationException e) {
    e.printStackTrace();
} catch (IllegalAccessException e) {
    e.printStackTrace();
} catch (InvocationTargetException e) {
    e.printStackTrace();


  try {
    Field a = Book.class.getDeclaredField("A");
    System.out.println(a.get(null));
    a.set(null, "AAAAA");
    System.out.println(a.get(null));
  } catch (NoSuchFieldException | IllegalAccessException e) {
    e.printStackTrace();
  }

  try {
    Field b = Book.class.getDeclaredField("B");
    b.setAccessible(true);
    System.out.println(b.get(book));
    b.set(book, "BBBB");
    System.out.println(b.get(book));
  } catch (NoSuchFieldException | IllegalAccessException e) {
    e.printStackTrace();
  }

  Method c = Book.class.getDeclaredMethod("c");
  c.setAccessible(true);
  try {

    c.invoke(book);
  } catch (IllegalAccessException e) {
    e.printStackTrace();
  } catch (InvocationTargetException e) {
    e.printStackTrace();
  }

  Method sum = Book.class.getDeclaredMethod("sum", int.class, int.class);
  try {
    int invoke = (int) sum.invoke(book, 1, 2);
    System.out.println(invoke);
  } catch (IllegalAccessException e) {
    e.printStackTrace();
  } catch (InvocationTargetException e) {
    e.printStackTrace();
  }

[결과]

A
AAAAA
MyBook
BBBB
C
3    

나만의 DI 프레임워크 만들기

@Inject 라는 애노테이션 만들어서 필드 주입 해주는 컨테이너 서비스 만들기

public class BookService {

    @Inject
    BookRepository bookRepository;

}

ContainerService.java

public static <T> T getObject(T ClassType)

  • classType에 해당하는 타입의 객체를 만들어 준다.
  • 단, 해당 객체의 필드 중에 @Inject가 있다면 해당 필드도 같이 만들어 제공한다.

ContainerService.java

public class ContainerService {
    public static <T> T getObject(Class<T> classType) {
        T newInstance = getNewInstance(classType);
        Arrays.stream(classType.getDeclaredFields()).forEach(field -> {
            if (field.getAnnotation(Inject.class) != null) {
                Object fieldInstance = getNewInstance(field.getType());
                field.setAccessible(true);
                try {
                    field.set(newInstance, fieldInstance);
                } catch (IllegalAccessException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        return newInstance;
    }

    private static <T> T getNewInstance(Class<T> classType) {
        try {
            return classType.getConstructor(null).newInstance();
        } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
            throw new RuntimeException(e);
        }
    }
}

BookService.java

public class BookService {

    @Inject
    BookRepository bookRepository;
}

BookRepository

public class BookRepository {
}

ContainerServiceTest.java

class ContainerServiceTest {
    @Test
    void getObject_BookRepository() {
        BookRepository object = ContainerService.getObject(BookRepository.class);
        assertNotNull(object);
    }

    @Test
    void getObject_BookService() {
        BookService bookService = ContainerService.getObject(BookService.class);
        assertNotNull(bookService);
        assertNotNull(bookService.bookRepository);
    }
}

Inject.java

@Retention(RetentionPolicy.RUNTIME)
public @interface Inject {

}

[참고]

블로그

[백기선님 인프런 강좌](

'JAVA' 카테고리의 다른 글

날짜 메서드 세련되게 작성해보자!  (0) 2022.05.15
JAVA 소소한 꿀팁  (0) 2021.12.23
팩토리메서드를 활용한 List → DTO  (0) 2021.07.29