삽질저장소

자바 reflection API

2021-01-10java

1. Java Reflection 정의

  • 리플렉션이란 객체를 통해 클래스의 정보를 분석해 내는 프로그램 기법을 말한다.
  • 스프링의 다음과 같은 코드가 reflection 을 이용한 예라고 볼 수 있다.

    • @Service, @Autowired 등과 같은 애노테이션을 사용해서, 직접 인스턴스를 생성해주지 않아도 쓸 수 있게 된다.
    • @Service
      public class BookService {
        @Autowired
        BookRepository bookRepository;
      }

2. 클래스 정보 조회

  • Class 에 접근하는 방법

    • 클래스.class 로 가져온다.

      Class<Book> bookClass = Book.class;
    • 인스턴스를 생성한 경우에는 getClass() 로 가져온다.

      Book book = new Book();
      Class<? extends Book> aClass = book.getClass();
    • FQCN((fully Qualified Class Name), 클래스가 속한 패키지명을 모두 포함한 클래스) 문자열로 클래스를 가져온다.

      Class<?> aClass1 = Class.forName("me.bingbingpa.Book");
  • Class 를 통해 할 수 있는 것

    • 필드 목록, 메소드 목록, 상위 클래스, 인터페이스, 애노테이션, 생성자 등등… 많은 것들을 조회 할 수 있다.
    • 다음과 같이 Book 클래스와 인스턴스가 있다고 했을 때, 아래의 코드와 같은 것들을 할 수 있다.

      Class<Book> bookClass = Book.class;
      Book book = new Book();
    • 클래스의 public 한 필드만 조회하기

      Arrays.stream(bookClass.getFields()).forEach(System.out::println);
    • 접근제어자 상관없이 모두 조회하기(특정 이름을 파라미터로 줄 수도 있다.)

      • private 한 값들까지 모드 접근하기 위해서는 setAccessible 값을 true 로 주어야 한다.

        Arrays.stream(bookClass.getDeclaredFields()).forEach(f -> {
          try {
              f.setAccessible(true);
              System.out.printf("%s %s\n", f, f.get(book));
          } catch (IllegalAccessException e) {
              e.printStackTrace();
          }
        });
    • modifiers : 접근제어자의 타입을 체크 할 수 있다.

      Arrays.stream(Book.class.getDeclaredFields()).forEach(f -> {
        int modifiers = f.getModifiers();
        System.out.println(f);
        System.out.println(Modifier.isPrivate(modifiers));
        System.out.println(Modifier.isStatic(modifiers));
      });
    • 메소드 조회하기

      Arrays.stream(bookClass.getMethods()).forEach(System.out::println);
    • 생성자 조회하기

      Arrays.stream(bookClass.getDeclaredConstructors()).forEach(System.out::println);
    • 슈퍼 클래스 조회하기(상속받는 클래스는 하나다.)

      Class<? super MyBook> superClass = MyBook.class.getSuperclass();
    • 인터페이스 조회하기

      Arrays.stream(MyBook.class.getInterfaces()).forEach(System.out::println);

3. 애노테이션과 리플렉션

  • 중요 애노테이션

    • @Retention: 해당 애노테이션을 언제까지 유지할 것인가? 소스, 클래스, 런타임
    • @Inherit: 해당 애노테이션을 하위 클래스까지 전달할 것인가?
    • @Target: 어디에 사용할 수 있는가를 정의한다. 메소드, 필드, 패키지 등등 다양한 곳에 적용 할 수 있다.

      • ElementType.ANNOTATION_TYPE
      • ElementType.CONSTRUCTOR
      • ElementType.FIELD
      • ElementType.LOCAL_VARIABLE
      • ElementType.METHOD
      • ElementType.PACKAGE
      • ElementType.PARAMETER
      • ElementType.TYPE
  • 리플렉션

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

4. 클래스 정보 수정 또는 실행

  • 클래스의 정보를 수정 또는 실행하기 위해서는 먼저 다음과 같이 클래스 인스턴스를 만들어야 한다.

    • Class.newtInstance() 는 deprecated 됐으며, 이제부터는 생성자를 통해서 만들어야 한다.
    • 아래 코드에서는 String class 를 받는 생성자로 인스턴스를 생성하기 때문에 String class 를 파라미터로 줘야한다.
    • public class Book {
        private String B = "B";
      
        public Book() { }
      
        public Book(String b) {
            B = b;
        }
      }
    • Class<?> bookClass = Class.forName("me.bingbingpa.Book");
      Constructor<?> constructor = bookClass.getConstructor(String.class);
      Book book = (Book) constructor.newInstance("mybook");
      System.out.println(book);
  • 리플렉션을 이용해서 특정 필드, 메소드 값 조회 변경

    Class<?> bookClass = Class.forName("me.bingbingpa.Book");
    Constructor<?> constructor = bookClass.getConstructor(String.class);
    Book book = (Book) constructor.newInstance("mybook");
    • static 한 값 조회 및 변경

      • set method 에서 첫번째 인자는 특정 인스턴스인데 여기서는 static 변수이기 때문에 인스턴스를 넘겨주지 않아도 된다.

        Field a = Book.class.getDeclaredField("A");
        System.out.println(a.get(null));
        a.set(null, "AAA");
        System.out.println(a.get(null));
    • 인스턴스의 필드 조회 및 변경

      Field b = Book.class.getDeclaredField("B");
      b.setAccessible(true); //private 값을 가져올때 true 로 설정해줘야 한다.
      System.out.println(b.get(book)); //인스턴스명을 파라미터로 줘야 한다.
      b.set(book, "BBBB");
      System.out.println(b.get(book));
    • private method 실행하기

      Method c = Book.class.getDeclaredMethod("c");
      c.setAccessible(true);
      c.invoke(book); // 메소드 실행
    • public method(parameter: int, int)

      Method sum = Book.class.getDeclaredMethod("sum", int.class, int.class);
      int invoke = (int)sum.invoke(book, 1, 2);// 메소드 실행
      System.out.println(invoke);

5. reflection API 를 이용해서 DI 프레임워크 만들기

  • inject 애노테이션 만들기

    @Retention(RetentionPolicy.RUNTIME)
    public @interface Inject {
    }
  • repository class 만들기

    public class BookRepository {
    }
  • service class 만들기

    public class BookService {
    
      @Inject
      BookRepository bookRepository;
    }
  • 자동 주입 해줄 ContainerService 만들기

    public class ContainerService {
    
      public static <T> T getObject(Class<T> classType) {
          T instance = createInstance(classType);
          Arrays.stream(classType.getDeclaredFields()).forEach(f-> {
              // @Inject 이 붙은 필드일 경우 인스턴스를 생성해준다.
              if (f.getAnnotation(Inject.class) != null) {
                  Object fieldInstance = createInstance(f.getType());
                  f.setAccessible(true);
                  try {
                      f.set(instance, fieldInstance);
                  } catch (IllegalAccessException e) {
                      throw new RuntimeException(e);
                  }
              }
          });
          return instance;
      }
    
      private static <T> T createInstance(Class<T> classType) {
          try {
              return classType.getConstructor(null).newInstance();
          } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
              throw new RuntimeException(e);
          }
      }
    }
  • 테스트

    • junit 을 통해 테스트 해보면 BookService 에 인터페이스로 선언된 bookRepository 가 null 이 아닌 것을 확인 할 수 있다.

      BookService bookService = ContainerService.getObject(BookService.class);
      assertNotNull(bookService);
      assertNotNull(bookService.bookRepository);

참조 링크

  • Thank You for Visiting My Blog, Have a Good Day 😆
    © 2021 Developer shPark, Powered By Gatsby.