티스토리 뷰
리플렉션(Reflection)이란?
리플렉션은 구체적인 클래스 타입을 알지 못하여도, 해당 클래스의 메소드, 타입, 필드(변수)들을
접근할 수 있도록 해주는 자바 API 입니다.
https://docs.oracle.com/javase/8/docs/api/java/lang/Class.html
먼저 리플렉션을 실습할 프로젝트를 생성해보겠습니다.
package me.whiteship;
public class Book {
private String a = "a";
private static String B = "BOOK";
private static final String C = "BOOK";
public String d = "d";
protected String e = "e";
public Book() {
}
public Book(String a, String d, String e) {
this.a = a;
this.d = d;
this.e = e;
}
private void f() {
System.out.println("f");
}
public void g() {
System.out.println("g");
}
public int h(){
return 100;
}
}
package me.whiteship;
public interface MyInterface {
}
package me.whiteship;
public class MyBook extends Book implements MyInterface{
public String extendA = "extend";
}
기본적인 실습 환경을 해보았습니다.
이제부터 리플렉션에 대해서 살펴보겠습니다.
package me.whiteship;
import java.util.Arrays;
public class App
{
public static void main( String[] args ) throws ClassNotFoundException {
/* 클래스 인스턴스 접근방법*/
Class<Book> bookClass = Book.class;
Book book = new Book();
Class<? extends Book> bookClass2 = book.getClass();
Class<?> bookClass3 = Class.forName("me.whiteship.Book");
}
}
리플렉션을 사용하려면 Class<T> 타입을 써야 합니다.
Class<T>에 접근하는 방법은 다음과 같이 3가지 방법이 있습니다.
1. 모든 클래스를 로딩 한 다음 Class<T>의 인스턴스가 생기므로, '타입. class'로 접근할 수 있습니다.
ex) Class<Book> bookClass = Book.class
2. 모든 인스턴스는 getClass() 메소드를 가지고 있습니다.
'인스턴스.getClass()'로 접근할 수 있습니다.
ex) Book book = new Book();
Class<? extends Book> bookClass2 = book.getClass();
3. 클래스를 문자열로 읽어오는 방법이 있습니다.
Class.forName("FQCN") // FQCN : 풀 패키지 클래스 경로
클래스 경로에 해당 클래스가 없다면 ClassNotFoundExcetion이 발생합니다.
ex) Class<?> bookClass3 = Class.forName("me.whiteship.Book");
package me.whiteship;
import java.lang.reflect.Modifier;
import java.util.Arrays;
public class App
{
public static void main( String[] args ) throws ClassNotFoundException {
// Field(변수) 가져오기
Class<Book> bookClass = Book.class;
Arrays.stream(bookClass.getFields()).forEach(System.out::println);
Book book = new Book();
Arrays.stream(book.getClass().getDeclaredFields()).forEach(field -> {
try {
field.setAccessible(true);
System.out.printf("%s : %s \n",field, field.get(book));
int modifier = field.getModifiers();
System.out.println(Modifier.isPublic(modifier));
} catch (IllegalAccessException e) {
e.printStackTrace();
}
});
// Method(메소드) 가져오기
Class<Book> bookClass2 = Book.class;
Arrays.stream(bookClass2.getMethods()).forEach(System.out::println);
//Constructor(생성자) 가져오기
Class<Book> bookClass3 = Book.class;
Arrays.stream(bookClass3.getDeclaredConstructors()).forEach(System.out::println);
//Super Class(상위 클래스) 가져오기
System.out.println(MyBook.class.getSuperclass());
//Interface(인터페이스) 가져오기
Arrays.stream(MyBook.class.getInterfaces()).forEach(System.out::println);
}
}
그럼 Class<T>를 통하여 할 수 있는 것이 무엇인지 위 코드와 함께 살펴보겠습니다.
1. 필드(목록) 가져오기
getField(String name) : 해당 public 변수를 가져옵니다.
getFields() : public 변수들을 가져옵니다.
getDeclaredField(String name) : 해당 변수를 가져옵니다.
getDeclaredFields() : Access Modifier(접근 제한자) 상관없이 변수들을 가져옵니다.
public 이외의 접근제한자 변수들에 접근하려면 Field 인스턴스가 setAccessible(true) 여야 합니다.
2. 메소드(목록) 가져오기
상속받은 모든 메소드를 가져옵니다.
getMethod(String name) : 해당 public 메소드를 가져옵니다.
getMethods() : public Method 들을 가져옵니다.
getDeclaredMethod(String name) : 해당 메소드를 가져옵니다.
getDeclaredMethods() : Access Modifier(접근 제한자) 상관없이 메소드들을 가져옵니다.
public 이외의 접근 제한자 메소드들을 접근하려면 Method 인스턴스가 setAccessible(true) 여야 합니다.
3. 생성자 가져오기
getConstructor(String name) : 해당 public Constructor를 가져옵니다.
getConstructors() : public Constructor 들을 가져옵니다.
getDeclaredConstructor(String name) : 해당 Constructor를 가져옵니다.
getDeclaredConstructors() : Access Modifier(접근 제한자) 상관없이 Constructor를 모두 가져옵니다.
public 이외의 접근 제한자 생성자들을 접근하려면 Constructor 인스턴스가 setAccessible(true) 여야 합니다.
4. 상위 클래스 가져오기
getSuperclass() : 해당 클래스의 상위(부모) 클래스를 가져옵니다.
5. 인터페이스 가져오기
getInterfaces() : 해당 클래스의 상속받은 인터페이스를 가져옵니다.
애노테이션(annotation), 생성자 등 그 외 여러 가지를 가져올 수 있습니다.
package me.whiteship;
import java.util.ArrayList;
class Book {
public static String A = "A";
private String B = "B";
public Book() {
}
public Book(String b) {
B = b;
}
private void c () {
System.out.println("C");
}
public int sum (int left, int right){
return left + right;
}
}
리플렉션 API를 이용하여 클래스 정보를 수정 및 실행하는 실습을 하기 전에 Book 클래스를 수정해 보겠습니다.
package me.whiteship;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class App
{
public static void main( String[] args ) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException {
Class<?> bookClass = Class.forName("me.whiteship.Book");
Constructor<?> constructor = bookClass.getConstructor(String.class);
Book book = (Book) constructor.newInstance("myBook");
System.out.println(book);
//Field(변수)
Field a = Book.class.getDeclaredField("A");
System.out.println(a.get(null));
a.set(null, "AAAAA");
System.out.println(a.get(null));
Field b = Book.class.getDeclaredField("B");
b.setAccessible(true);
System.out.println(b.get(book));
b.set(book, "BBBBB");
System.out.println(b.get(book));
// Method(메소드)
Method c = Book.class.getDeclaredMethod("c");
c.setAccessible(true); // Access Modifier가 private
c.invoke(book);
Method d = Book.class.getDeclaredMethod("sum", int.class, int.class);
int invoke = (int) d.invoke(book, 10, 10);
System.out.println(invoke);
}
}
먼저 Book클래스 인스턴스를 생성해보겠습니다.
위 코드와 같이 클래스를 문자열로 읽고서 생성자 변수를 따로 만들어 인스턴스를 생성할 수도 있습니다.
또한 bookClass.getConstructor(String.class).newInstance("myBook")으로 생성할 수도 있습니다.
인스턴스는 생성자를 통하여 생성할 수 있습니다.
ex) getConsturcor().newInstance();
a.get(Object obj), a.set(Object obj, Object value)에 첫 번째 parameter(매개변수)인 obj는 해당 Instance를 의미합니다.
a변수는 Book의 static변수인 A를 의미하여 Instance 없이 값을 가져올 수 있기 때문에 null 값이 매개변수로 들어가는 것입니다.
그리고 b변수는 Book의 static 변수가 아니기 때문에 Instance를 생성해야 변수가 선언 및 초기화가 되기 때문에
Book 클래스의 Instance를 생성한 book변수가 매개변수로 들어가는 것입니다.
Method 실행은 Method 클래스의 invoke 메소드를 사용하면 됩니다.
invoke(Object obj, Object... args)에 첫 번째 매개변수는 마찬가지로 Instance를 의미합니다.
d변수는 Book의 sum 메소드를 의미하기 때문에
d.invoke(book, 10, 10) 코드를 실행하면 반환 값으로 20이 나옵니다.
이로써 공부한 내용을 간략히 정리해보았습니다.
감사합니다.
출처
https://www.inflearn.com/course/the-java-code-manipulation
'Java' 카테고리의 다른 글
프록시 패턴(Proxy Pattern) (0) | 2022.04.05 |
---|---|
자바 리플렉션(Reflection)2 - Annotation, Dependency Injection(DI) (0) | 2022.04.04 |
바이트 코드를 조작하는 방법2 - ByteBuddy, Javaagent (1) | 2022.03.30 |
바이트 코드를 조작하는 방법 - 코드 커버리지 (0) | 2022.03.29 |
JVM 구조 (0) | 2022.03.28 |
- Total
- Today
- Yesterday
- java abstractprocessor
- 프록시패턴
- classloder
- 람다표현식
- java proxy pattern
- javaagent
- 깃 기초
- Functional Interface
- 실행 엔진
- jvm
- JVM 구조
- 리플렉션
- 바이트 코드
- 애노테이션 프로세서
- bytebuddy
- 코드 커버리지
- JRE와 JDK의 차이점
- 자바 프록시 패턴
- 자바 리플렉션
- 애노테이션
- java11 optional
- 클래스로더
- java
- dromos
- optional api
- Reflection
- javassist
- Annotation Processor
- java optional
- Java Reflection
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |