notepad

리플렉션 본문

JAVA/JAVA

리플렉션

likewise_ 2020. 9. 4. 22:05

클래스를 조작하는 기술 / 클래스의 정보를 분석하고 조작하는 기술

  1. 스프링 DI는 어떻게 동작할까?

    • bookRepository 인스턴스는 어떻게 null이 아닌걸까?
    • 스프링은 어떻게 BookService 인스턴스에 BookRepository 인스턴스를 넣어준 것일까?
  2. 리플렉션 API을 사용하여 정보를 참조하는 방법

  • 클래스 정보 조회 Class (Java Platform SE 8 )

              //클래스 로딩이 끝나면 클래스 타입의 인스턴스를 만들에서 힙에 저장한다
          //인스턴스는 클래스를 로딩만 해도 인스턴스가 만들어진다.
          Class<Book> bookClass = Book.class; //타입으로 가져올 때
    
          Book book = new Book(); //인스턴스로 가져올 때
          Class<? extends Book> aClass = book.getClass();
    //
    //        //FQCN
          Class<?> aClass1 = Class.forName("reflection.Book");
    
          Field[] fields = bookClass.getDeclaredFields();
          Arrays.stream(fields).forEach(
                  f -> {
                      f.setAccessible(true); //접근 지시자 무시 | 세팅 안 할 경우 private 등은 접근 불가
                      try {
                          System.out.println(f + ",\n" + f.get(book));
                          System.out.println();
                      } catch (IllegalAccessException e) {
                          e.printStackTrace();
                      }
                  }
          );
    
          System.out.println("-----------------");
          Arrays.stream(bookClass.getMethods()).forEach(System.out::println);
          System.out.println("-----------------");
    
          Arrays.stream(Book.class.getDeclaredFields())
                  .forEach(field -> {
                      int modifiers = field.getModifiers();
                      System.out.println("modifiers = " + modifiers);
                      System.out.println(field);
                      System.out.println(Modifier.isPrivate(modifiers));
                      System.out.println(Modifier.isStatic(modifiers));
                  });
          System.out.println("-----------------");
  1. 애노테이션과 리플렉션
  • 중요 애노테이션

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

  • 리플렉션

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

// annotation은 comment와 같다. 정보가 기본적으로 소스, 클래스까지만 남고,
// 메모리상/바이트코드에는 남지 않는다. 읽고 싶으면 @Retention 에 런타임 범위를 설정해준다.
// 기본값은 클래스
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.FIELD})
@Inherited
public @interface MyAnnotation {

    // 값을 하나만 받을 때는 필드면을 value()로 쓰면 호출시 생략 가능
    String value() default "123";

    String name() default "jeeell";
    int age() default 0;
}

@MyAnnotation
public class Book {}


public class App {
    public static void main(String[] args) {
//        Annotation[] declaredAnnotations = MyBook.class.getAnnotations();
//        Arrays.stream(declaredAnnotations)
//                .forEachOrdered(System.out::println);

        Arrays.stream(Book.class.getDeclaredFields())
                .forEach(field -> {
                    //Arrays.stream(field.getAnnotations()).forEach(System.out::println);
                    Arrays.stream(field.getAnnotations()).forEach(
                            annotation -> {
                                if(annotation instanceof MyAnnotation){
                                    MyAnnotation myAnnotation = (MyAnnotation) annotation;
                                    System.out.println("name = " + myAnnotation.name());
                                    System.out.println("age = " + myAnnotation.age());
                                }
                            }
                    );

                });
    }
}
  • 리플렉션 클래스 정보 수정 또는 실행

    Class<?> bookClass = Class.forName("annotation.Book");
    

//Instance 생성 bookClass.newInstance(); -> deprecated 됨 | 생성자를 통해서 생성
Constructor<?> constructor = bookClass.getConstructor(String.class);
Book book = (Book) constructor.newInstance("String.class");
System.out.println("book = " + book);

//field 값 변경 - static
Field a = Book.class.getDeclaredField("a");
a.set(null, "AAAAA");
System.out.println("a = " + a.get(null));

//field 값 조회
Field b = Book.class.getDeclaredField("b");
b.setAccessible(true); //접근 제어자 상괸없이 접근 가능
System.out.println("b = " + b.get(book));

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


4. Custom DI
- 
```java
public static <T> T getObject(Class<T> classType) {
    T instance = createInstance(classType); //클래스를 받아서 인스턴스를 생성
        //생성한 인스턴스의 필드 조회
    Field[] declaredFields = classType.getDeclaredFields();

    Arrays.stream(declaredFields).forEach(
            field -> {
                  //@Inject 어노테이션을 읽어서
                Inject annotation = field.getAnnotation(Inject.class);
                if (null != annotation) { //해당 어노테이션이 있으면
                        //해당 타입의 인스턴스 생성 후 접근 제어 해제
                    Object fieldInstance = createInstance(field.getType());
                    field.setAccessible(true);
                    try {
                            //전달받은 인스턴스에 @Inject 가 적용된 필드 인스턴스 주입
                        field.set(instance, fieldInstance); //Inject -> DI
                    } catch (IllegalAccessException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
    );
    return instance;
}

private static <T> T createInstance(Class<T> classType) {
    try {
        return classType.getConstructor().newInstance();
    } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
        throw new RuntimeException();
    }
}
  1. 리플렉션 사용시 주의할 것
  • 지나친사용은성능이슈를야기할수있다.반드시필요한경우에만사용할것
  • 컴파일 타임에 확인되지 않고 런타임 시에만 발생하는 문제를 만들 가능성이 있다.
  • 접근 지시자를 무시할 수 있다.

'JAVA > JAVA' 카테고리의 다른 글

[레시피2] 스프링코어 - POJO와 IOC컨테이너를 다루는 기술  (0) 2021.03.10
JVM 이해하기  (0) 2020.09.01
Generics - 제네릭 사용 이유  (0) 2020.05.27
Comments