클래스 정보 조회
- 리플렉션의 시작은 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
** 참고 더 자바, 코드를 조작하는 다양한 방법, 백기선님 강연