클래스 정보 조회
- 리플렉션의 시작은 Class- https://docs.oracle.com/javase/8/docs/api/java/lang/Class.html
 
- Class에 접근하는 방법 - 모든 클래스를 로딩한 다음 Class인스턴스가 생긴다. "타입.class"로 접근할 수 있다. 
- 모든 인스턴스는 getClass() 메소드를 가지고 있다. “인스턴스.getClass()”로 접근할 수 있다.
- 클래스를 문자열로 읽어오는 방법
        - Class.forName(“FQCN”) // 풀패키지클래스네임
- 클래스패스에 해당하는 클래스가 없다면 ClassNotFoundException이 발생한다.
 public static void main(String[] args) throws ClassNotFoundException { Class<Book> bookClass = Book.class; Book book = new Book(); Class<? extends Book> aClass = book.getClass(); // Class aClass = (Class<Book>)bok.getClass(); Class<?> bookClass2 = Class.forName("com.study.Book"); // Class<Book> bookClass2 = (Class<Book>)Class.forName("com.study.Book"); }
- Class를 통해 가져올 수 있는 것 - 필드(목록) 가져오기
            - class.getFields() : public 메서드만 가져온다.
- class.getDeclaredFields(): 접근지시자에 상관없이 가져온다. (field에 setAccessible을 true로 주면 접근지시자에 접근을 가능하도록 한다.)
 // public 만 가져온다 Arrays.stream(bookClass.getFields()).forEach(System.out::println); // 모든 필드 중 a 이름의 필드및 값을 가져온다. // accessible을 true로 선택하면 접근지시자를 무시할 수 있다. Book book = new Book(); Arrays.stream(bookClass.getDeclaredFields()).forEach(f -> { try { f.setAccessible(true); System.out.println(String.format("%s %s", f, f.get(book))); int modifier = f.getModifiers(); System.out.println(Modifier.isPrivate(modifier)); System.out.println(Modifier.isStatic(modifier)); } catch(IllegalAccessException e) { e.printStackTrace(); } });
- 메소드(목록) 가져오기
            - getMethods(): 부모 객체에서 선언한 method도 가져온다.
- getDeclaredMethods(): 해당 객체에 선언된 method만 가져온다.
 // 메소드 Arrays.stream(MyBook.class.getMethods()).forEach(m -> { int modifier = m.getModifiers(); System.out.println(Modifier.isAbstract(modifier)); });
- 
            상위 클래스 가져오기 // 상위 클래스를 가져올 수 있다. System.out.println(MyBook.class.getSuperclass());
- 
            인터페이스(목록) 가져오기 // 인터페이스 Arrays.stream(MyBook.class.getInterfaces()).forEach(System.out::println);
- 
            애노테이션 가져오기 Arrays.stream(bookClass.getAnnotations()).forEach(System.out::println);
- 
            생성자 가져오기 // 생성자 가져오기 Arrays.stream(bookClass.getConstructors()).forEach(System.out::println);
- 등등등
 
- 필드(목록) 가져오기
            
 
- 모든 클래스를 로딩한 다음 Class
Annotation과 Reflection
- 중요 Annotation
    @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE, ElementType.FIELD}) @Inherited public @interface MyAnnotation { String value(); // value는 속성설정할 때 이름을 할당한할 수 있다. int max() default 100; }- @Retention: 해당 애노테이션을 언제까지 유지할 것인가? 소스, 클래스, 런타임
        - RetentionPolicy.CLASS, RetentionPolicy.SOURCE, RetentionPolicy.RUNTIME
 
- @Inherited: 해당 애노테이션을 하위 클래스까지 전달할 것인가
        - 
            Book 클래스에 붙은 MyAnnotation이 하위 클래스인 MyBook까지 전달받는 것을 확인할 수 있다. @MyAnnotation public class Book {}public class MyBook extends Book {}public static void main(String[] args) { Arryas.stream(MyBook.class.getAnnotations()).forEach(System.out:println); // @com.study.MyAnnotation(max=20, value="HELLO") }
 
- 
            
- @Target: 어디에 사용할 수 있는가?
        - ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.FIELD 등 Annotation을 사용할 수 있는 위치를 정하여 제한할 수 있다.
 
 
- @Retention: 해당 애노테이션을 언제까지 유지할 것인가? 소스, 클래스, 런타임
        
- Reflection
    - getAnnotation(): 상속받은 (@Inherited) 애노테이션까지 조회
        - 
            예제) fields에 붙은 Annotation이 MyAnnotation 타입인지 확인한 후 value와 number를 출력 public static void main(String args) { Arrays.stream(Book.class.getDeclaredFields()).forEach(f -> { Arrays.stream(f.getAnnotations()).forEach(a -> { if(a instanceof MyAnnotation) { MyAnnotation myAnnotation = (MyAnnotation)a; System.out.println(myAnnotation.value()); System.out.println(myAnnotation.number()); } }); }); }
 
- 
            
- getDeclaredAnnotations(): 자기 자신에만 붙어있는 애노테이션 조회
 
- getAnnotation(): 상속받은 (@Inherited) 애노테이션까지 조회
        
클래스 정보 수정
- Class 인스턴스 만들기
    - Class.newInstance()는 deprecated 됐으며, 이제부터는 생성자를 통해서 만든다.
 
- 생성자로 인스턴스 만들기
    - Constructor.newInstance(params)
 
- 필드 값 접근하기/설정하기
    - 특정 인스턴스가 가지고 있는 값을 가져오는 것이기 때문에 인스턴스가 필요하다.
- Field.get(object)
- Field.set(object, value)
- static 필드를 가져올 때는 object가 없어도 되니까 null을 넘기면 된다.
 
- 메소드 실행하기
    - Object Method.invoke(object, params)
 
- 
    예제 Class<?> bookClass = Class.forName("com.study.Book"); Constructor<?> constructor = bookClass.getConstructor(String.class); Book book = (Book)constructor.newInstance("myBook"); System.out.println(book); Field f = Book.class.getDeclaredField("B"); f.setAccessible(true); System.out.println(f.get(book)); // myBook f.set(book, "myBook2"); System.out.println(f.get(book)); // myBook2 Method method = Book.class.getDeclaredMethod("sum", int.class, int.class); method.setAccessible(true); int invoke = (int)method.invoke(book, 1, 2); // book의 C 메소드 실행, return 받을 수 있고, 캐스팅 가능 System.out.println(invoke); // 3
나만의 DI 프레임워크 만들어보기
- 
    @Inject라는 애노테이션을 만들어서 필드 주입해주는 컨테이너 서비스 만들기 public class BookService { @Inject BookRepository bookRepository }
- 
    ContainerService.java - classType에 해당하는 타입의 객체를 만들어 준다.
- 단, 해당 객체의 필드 중에 @Inject가 있다면, 해당 필드도 같이 만들어 제공한다.
        private static <T> T createInstance(Class<T> classType) { try { return classType.getConstructor().newInstance(); } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException e) { // TODO Auto-generated catch block throw new RuntimeException(e); } } public static <T> T getObject(Class<T> classType) { T instance = createInstance(classType); Arrays.stream(classType.getDeclaredFields()).forEach(field -> { if(field.getAnnotation(Inject.class) != null) { Object fieldInstance = createInstance(field.getType()); field.setAccessible(true); try { field.set(instance, fieldInstance); } catch (IllegalArgumentException | IllegalAccessException e) { // TODO Auto-generated catch block throw new RuntimeException(); } } }); return instance; }
- 
        테스트 코드 @Test public void getObjectTest() { BookRepository bookRepository = ContainerService.getObject(BookRepository.class); assertNotNull(bookRepository); } @Test public void getObjectInjectionTest() { BookService bookService = ContainerService.getObject(BookService.class); BookRepository bookRepository = bookService.bookRepository; assertNotNull(bookRepository); }
 
정리
- 리플렉션 사용시 주의할 점
    - 지나친 사용은 성능 이슈를 야기할 수 있다. 반드시 필요할 경우에만 사용
- 컴파일 타임에 확인되지 않고 런타임 시에만 발생하는 문제를 만들 가능성이 있다.
- 접근 지시자를 무시할 수 있다.
 
- 스프링
    - 의존성 주입
- MVC 뷰에서 넘어온 데이터를 객체에 바인딩할 때
 
- 하이버네이트
    - @Entity 클래스에 Setter가 없다면 리플렉션을 사용한다.
 
- JUnit
    - https://junit.org/junit5/docs/5.0.3/api/org/junit/platform/commons/util/ReflectionUtils.html
 
** 참고 https://docs.oracle.com/javase/tutorial/reflect/index.html
** 참고 더 자바, 코드를 조작하는 다양한 방법, 백기선님 강연