자바 리플렉션[Reflection] 이란??
2021. 7. 14. 17:16ㆍJAVA
리플렉션(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 |