reflection api
인프런 백기선님의 강의 <더 자바, 코드를 조작하는 다양한 방법> 을 수강하면서 공부한 내용을 정리합니다.
1. Class API 로 클래스 정보 조회하는 법 익히기
package me.flash;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import java.lang.reflect.Modifier;
import java.util.Arrays;
class MainTest {
@Test
@DisplayName("1. Class<T>에_접근하는 3가지 방법")
void approachClass() throws ClassNotFoundException {
//1. 모든 클래스를 로딩하고 나면, Class<T> 의 인스턴스가 생긴다. 타입.class 형태로 접근 가능하다.
Class<Number> numberClass1 = Number.class;
//2. 모든 인스턴스는 getClass() 메소드를 가지고 있다. 인스턴스.getClass() 로 접근할 수 있다.
Number number = new Number();
Class<? extends Number> numberClass2 = number.getClass();
//3. 클래스 full qualified class name 인 문자열로 읽어오는 방법 : Class.forName("FQCN");
//- 클래스 패스에 해당 클래스가 없다면 ClassNotFoundException 발생한다.
Class<?> numberClass3 = Class.forName("me.flash.Number");
System.out.println(numberClass1);
System.out.println(numberClass2);
System.out.println(numberClass3);
System.out.println();
//class me.flash.Number
//class me.flash.Number
//class me.flash.Number
}
@DisplayName("2. 필드 목록 가져오기")
@Test
void getFields() {
Class<Number> numberClass = Number.class;
Arrays.stream(numberClass.getFields()).forEach(System.out::println); //public 필드만
System.out.println();
Arrays.stream(numberClass.getDeclaredFields()).forEach(System.out::println); //모든 필드
System.out.println();
}
@DisplayName("2-1. 필드 값 가져오기")
@Test
void getFieldsValue() {
Class<Number> numberClass = Number.class;
Number number = new Number();
Arrays.stream(numberClass.getDeclaredFields()).forEach(f -> {
f.setAccessible(true);
System.out.println(f);
try {
System.out.println(f.get(number)); //instance 를 통해서 가져올 수 있다.
} catch (IllegalAccessException e) {
e.printStackTrace();
}
});
}
@DisplayName("2-2. 필드 접근제어자 확인하기")
@Test
void getFieldsModifier() {
Class<Number> numberClass = Number.class;
Number number = new Number();
Arrays.stream(numberClass.getDeclaredFields()).forEach(f -> {
int modifiers = f.getModifiers();
System.out.println(f);
System.out.println(Modifier.isPrivate(modifiers));
System.out.println(Modifier.isPublic(modifiers));
});
}
@DisplayName("3. 메소드 목록 가져오기")
@Test
void getMethods() {
Class<Number> numberClass = Number.class;
Arrays.stream(numberClass.getMethods()).forEach(System.out::println);
System.out.println();
Arrays.stream(numberClass.getDeclaredMethods()).forEach(System.out::println);
System.out.println();
}
@DisplayName("3-1. 메소드 관련 정보 가져오기")
@Test
void getMethodInfos() {
Class<Number> numberClass = Number.class;
Arrays.stream(numberClass.getDeclaredMethods()).forEach(f -> {
System.out.println(f);
Arrays.stream(f.getParameterTypes()).forEach(System.out::println);
System.out.println(f.getReturnType());
});
}
@DisplayName("4. 상위 클래스 가져오기")
@Test
void getSuperClass() {
Class<Number> numberClass = Number.class;
System.out.println(numberClass.getSuperclass());
}
@DisplayName("5. 인터페이스 목록 가져오기")
@Test
void getInterface() {
Arrays.stream(MyNumber.class.getInterfaces()).forEach(System.out::println);
//interface me.flash.NumberInterface
}
@DisplayName("6. 어노테이션 가져오기")
@Test
void getAnnotation() {
Arrays.stream(MyNumber.class.getAnnotations()).forEach(System.out::println);
}
@DisplayName("7. 생성자 가져오기")
@Test
void getConstructor() {
Arrays.stream(Number.class.getConstructors()).forEach(System.out::println);
//public me.flash.Number()
//public me.flash.Number(java.lang.String,java.lang.String,java.lang.String)
}
}
2. 리플렉션 API 와 어노테이션
@Retention(RetentionPolicy.RUNTIME) //default : RetentionPolicy.CLASS
@Target({ElementType.TYPE, ElementType.METHOD}) //생성자, 메소드에만 적용 가능
@Inherited
public @interface FlashAnnotation {
}
@Retention(RetentionPolicy.RUNTIME)
public @interface SecondAnnotation {
}
@FlashAnnotation
public class Number { ... }
@DisplayName("6. 어노테이션 가져오기")
@Test
void getAnnotation() {
Arrays.stream(MyNumber.class.getAnnotations()).forEach(System.out::println);
}
@DisplayName("6-1. 어노테이션 목록 조회하기")
@Test
void getAnnotationList() {
/**
* 커스텀 어노테이션은 java code, java byte code (class file)까지도 남아있지만, runtime 까지 남아있지는 않는다.
* 그래서 getAnnotations() 에서는 사용자 정의 어노테이션은 조회되지 않는다.
* 조회 방법 : @Retention 설정하기
* */
Arrays.stream(Number.class.getAnnotations()).forEach(System.out::println);
//@me.flash.FlashAnnotation()
}
@DisplayName("6-2. @Inherited 상속 클래스에서 상위 클래스 어노테이션 조회")
@Test
void getSuperClassAnnotation() {
Arrays.stream(MyNumber.class.getAnnotations()).forEach(System.out::println);
//@me.flash.FlashAnnotation()
}
@DisplayName("6-3. 상속받은 어노테이션까지 조회하기/하지않기")
@Test
void getAnnotations() {
Arrays.stream(MyNumber.class.getAnnotations()).forEach(System.out::println);
System.out.println();
//@me.flash.FlashAnnotation()
//@me.flash.SecondAnnotation()
Arrays.stream(MyNumber.class.getDeclaredAnnotations()).forEach(System.out::println);
//@me.flash.SecondAnnotation()
}
3. Reflection API 로 클래스 정보 수정하거나 실행하기
package me.flash;
public class ClassModification {
public static String A = "A";
private String b = "B";
private void c() {
System.out.println("C");
}
public int d (int num1, Integer num2) {
return num1 + num2;
}
public ClassModification() {
}
public ClassModification(String b) {
this.b = b;
}
}
package me.flash;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
class ClassModificationTest {
@DisplayName("class instance 만들기")
@Test
void createInstance() throws InstantiationException, IllegalAccessException {
/**
* 주의 : ClassModification.class.newInstance(); 는 Deprecated 되었으므로 사용하지 않도록 한다.
* */
ClassModification classModification = ClassModification.class.newInstance();
System.out.println(classModification); //me.flash.ClassModification@2f4948e4
}
@DisplayName("생성자로 인스턴스 만들기")
@Test
void constructorInstance() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
Class<?> clazz = Class.forName("me.flash.ClassModification");
Constructor<?> constructor = clazz.getConstructor(null);
ClassModification classModification = (ClassModification) constructor.newInstance();
System.out.println(classModification); //me.flash.ClassModification@2f4948e4
}
@DisplayName("static 필드 접근하기, 수정하기")
@Test
void editStaticFields() throws NoSuchFieldException, IllegalAccessException {
Field a = ClassModification.class.getDeclaredField("A");
System.out.println(a.get(null));
a.set("A", "hey");
System.out.println(a.get(null));
}
@DisplayName("instance 필드 접근하기, 수정하기")
@Test
void editInstanceFields() throws ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchMethodException, NoSuchFieldException {
Class<?> aClass = Class.forName("me.flash.ClassModification");
Constructor<?> constructor = aClass.getConstructor(String.class);
ClassModification instance = (ClassModification) constructor.newInstance("hello");
Field b = ClassModification.class.getDeclaredField("b");
b.setAccessible(true);
System.out.println(b.get(instance));
b.set(instance, "bye");
System.out.println(b.get(instance));
}
@DisplayName("메소드 실행하기")
@Test
void methodExecution() throws NoSuchMethodException, ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException {
Class<?> aClass = Class.forName("me.flash.ClassModification");
Constructor<?> constructor = aClass.getConstructor(String.class);
ClassModification instance = (ClassModification) constructor.newInstance("hello");
Method c = ClassModification.class.getDeclaredMethod("c");
c.setAccessible(true);
c.invoke(instance);
Method d = ClassModification.class.getDeclaredMethod("d", int.class, Integer.class);
System.out.println(d.invoke(instance, 1, 2));
}
}
Last updated