-
Notifications
You must be signed in to change notification settings - Fork 0
김현준, 홍은기 리플렉션
구체적인 Class Type을 알지 못하더라도 해당 Class의 method, type, variable들에 접근할 수 있도록 해주는 자바 API이며,
컴파일된 바이트 코드를 통해 Runtime에 동적으로 특정 Class의 정보를 추출할 수 있는 프로그래밍 기법이다.
-
Field
클래스clazz.getDeclaredFields()
-
getType()
- 이 필드 객체가 나타내는 필드의 클래스 타입을 반환합니다.
-
getGenericType()
- 이 필드 객체가 나타내는 필드의 제네릭 타입을 반환합니다.
-
getName()
- 이 필드 객체가 나타내는 필드의 이름을 반환합니다.
-
getModifiers()
- 이 필드 객체가 나타내는 필드의 접근 수정자를 정수 값으로 반환합니다.
-
isSynthetic()
- 이 필드가 합성 필드인지 여부를 반환합니다.
-
set(Object obj, Object value)
- 지정된 객체의 이 필드 값을 value로 설정합니다.
- 예외: IllegalAccessException, IllegalArgumentException
-
get(Object obj)
- 지정된 객체의 이 필드 값을 반환합니다.
- 예외: IllegalAccessException, IllegalArgumentException
-
Constructor
클래스-
getName()
- 생성자의 이름을 반환합니다. 일반적으로 클래스의 이름과 동일합니다.
-
getModifiers()
- 생성자의 접근 제어자 및 기타 수정자를 비트 필드 형식으로 반환합니다.
Modifier
클래스의 정적 메서드를 사용하여 이 값을 해석할 수 있습니다.
- 생성자의 접근 제어자 및 기타 수정자를 비트 필드 형식으로 반환합니다.
-
getParameterTypes()
- 생성자의 파라미터 타입들을
Class<?>
배열로 반환합니다.
- 생성자의 파라미터 타입들을
-
getExceptionTypes()
- 생성자가 던질 수 있는 예외 타입들을
Class<?>
배열로 반환합니다.
- 생성자가 던질 수 있는 예외 타입들을
-
newInstance(Object... initargs)
- 지정된 인수로 생성자를 호출하여 새로운 인스턴스를 생성합니다. 이 메서드는 생성자가 던질 수 있는 모든 예외를 던질 수 있습니다.
-
isVarArgs()
- 생성자가 가변 인수(varargs) 파라미터를 사용하는지 여부를 반환합니다.
-
isSynthetic()
- 생성자가 합성(synthetic) 생성자인지 여부를 반환합니다. 합성 생성자는 컴파일러가 내부 용도로 생성한 생성자를 의미합니다.
-
getDeclaringClass()
- 이 생성자를 선언한 클래스를 반환합니다.
-
getAnnotatedReturnType()
- 생성자의 반환 타입에 대한 주석 정보를 포함하는
AnnotatedType
객체를 반환합니다.
- 생성자의 반환 타입에 대한 주석 정보를 포함하는
-
getParameterAnnotations()
- 생성자의 각 파라미터에 대한 주석을 2차원 배열로 반환합니다. 각 파라미터는 여러 주석을 가질 수 있습니다.
-
getAnnotatedParameterTypes()
- 생성자의 각 파라미터 타입에 대한 주석 정보를 포함하는
AnnotatedType
배열을 반환합니다.
- 생성자의 각 파라미터 타입에 대한 주석 정보를 포함하는
-
-
Method
클래스-
getName()
- 메서드의 이름을 반환합니다.
-
getReturnType()
- 메서드의 반환 타입을
Class<?>
객체로 반환합니다.
- 메서드의 반환 타입을
-
getParameterTypes()
- 메서드의 파라미터 타입들을
Class<?>
배열로 반환합니다.
- 메서드의 파라미터 타입들을
-
getExceptionTypes()
- 메서드가 던질 수 있는 예외 타입들을
Class<?>
배열로 반환합니다.
- 메서드가 던질 수 있는 예외 타입들을
-
getModifiers()
- 메서드의 접근 제어자 및 기타 수정자를 비트 필드 형식으로 반환합니다.
Modifier
클래스의 정적 메서드를 사용하여 이 값을 해석할 수 있습니다.
- 메서드의 접근 제어자 및 기타 수정자를 비트 필드 형식으로 반환합니다.
-
invoke(Object obj, Object... args)
- 지정된 객체에서 이 메서드를 호출합니다.
args
는 메서드에 전달할 인수입니다.
- 지정된 객체에서 이 메서드를 호출합니다.
-
isVarArgs()
- 메서드가 가변 인수(varargs) 파라미터를 사용하는지 여부를 반환합니다.
-
isBridge()
- 메서드가 브리지(bridge) 메서드인지 여부를 반환합니다. 브리지 메서드는 제네릭 타입의 형 변환을 처리하기 위해 컴파일러가 생성하는 메서드입니다.
-
isSynthetic()
- 메서드가 합성(synthetic) 메서드인지 여부를 반환합니다. 합성 메서드는 컴파일러가 내부 용도로 생성한 메서드를 의미합니다.
-
isDefault()
- 메서드가 인터페이스의 기본 메서드인지 여부를 반환합니다.
-
getDeclaringClass()
- 이 메서드를 선언한 클래스를 반환합니다.
-
getGenericReturnType()
- 메서드의 제네릭 반환 타입을 반환합니다.
-
getGenericParameterTypes()
- 메서드의 제네릭 파라미터 타입들을 반환합니다.
-
getGenericExceptionTypes()
- 메서드가 던질 수 있는 제네릭 예외 타입들을 반환합니다.
-
getAnnotations()
- 메서드에 있는 모든 주석을 반환합니다.
-
getDeclaredAnnotations()
- 메서드에 선언된 모든 주석을 반환합니다.
-
getAnnotation(Class<T> annotationClass)
- 메서드에 지정된 타입의 주석이 있는지 확인하고, 있다면 반환합니다.
-
getParameterAnnotations()
- 메서드의 각 파라미터에 대한 주석을 2차원 배열로 반환합니다. 각 파라미터는 여러 주석을 가질 수 있습니다.
-
getAnnotatedReturnType()
- 메서드의 반환 타입에 대한 주석 정보를 포함하는
AnnotatedType
객체를 반환합니다.
- 메서드의 반환 타입에 대한 주석 정보를 포함하는
-
getAnnotatedParameterTypes()
- 메서드의 각 파라미터 타입에 대한 주석 정보를 포함하는
AnnotatedType
배열을 반환합니다.
- 메서드의 각 파라미터 타입에 대한 주석 정보를 포함하는
-
getDefaultValue()
- 메서드가 애너테이션 타입의 요소인 경우, 해당 요소의 기본 값을 반환합니다.
-
-
Parameter
클래스-
boolean equals(Object obj)
- 이 매개변수가 지정된 객체와 동일한지 여부를 확인합니다.
-
String getName()
- 이 매개변수의 이름을 반환합니다. (컴파일 옵션에 따라 이름이 저장되지 않을 수 있습니다.)
-
Type getParameterizedType()
- 이 매개변수의 파라미터화된 타입을 반환합니다. (예:
List<String>
)
- 이 매개변수의 파라미터화된 타입을 반환합니다. (예:
-
int getModifiers()
- 이 매개변수의 수정자를
int
값으로 반환합니다. (예:final
)
- 이 매개변수의 수정자를
-
Class<?> getType()
- 이 매개변수의 타입을
Class
객체로 반환합니다. (예:String.class
)
- 이 매개변수의 타입을
-
AnnotatedType getAnnotatedType()
- 이 매개변수의 주석 타입을 반환합니다. (Java 8에서 주석의 타입 정보를 포함하여 반환)
-
<T extends Annotation> T getAnnotation(Class<T> annotationClass)
- 지정된 주석 타입의 주석을 반환합니다. 주석이 없는 경우
null
을 반환합니다
- 지정된 주석 타입의 주석을 반환합니다. 주석이 없는 경우
-
Annotation[] getAnnotations()
- 이 매개변수에 있는 모든 주석을 배열로 반환합니다.
-
Annotation[] getDeclaredAnnotations()
- 이 매개변수에 선언된 모든 주석을 배열로 반환합니다. 상속된 주석은 포함되지 않습니다.
-
boolean isNamePresent()
- 이 매개변수의 이름이 런타임에 사용할 수 있는지 여부를 반환합니다.
-
boolean isImplicit()
- 이 매개변수가 암시적인지 여부를 반환합니다. 암시적 매개변수는 컴파일러가 추가하는 매개변수입니다.
-
boolean isSynthetic()
- 이 매개변수가 합성인지 여부를 반환합니다. 합성 매개변수는 일반적으로 컴파일러가 생성한 것입니다.
-
boolean isVarArgs()
- 이 매개변수가 가변 인수(
varargs
)인지 여부를 반환합니다.
- 이 매개변수가 가변 인수(
-
요구사항 1 - 클래스 정보 출력(코드)
@Test
@DisplayName("테스트1: 리플렉션을 이용해서 클래스와 메소드의 정보를 정확하게 출력해야 한다.")
public void showClass() throws Exception {
SoftAssertions s = new SoftAssertions();
Class<Question> clazz = Question.class;
logger.debug("Classs Name {}", clazz.getName());
constructor();
method();
fields();
}
@Test
public void fields() {
Class<Question> clazz = Question.class;
for (Field declaredField : clazz.getDeclaredFields()) {
logger.debug("Field = {} {}", declaredField.getType(), declaredField.getName());
}
}
@Test
public void constructor() throws Exception {
Class<Question> clazz = Question.class;
Constructor[] constructors = clazz.getConstructors();
for (Constructor constructor : constructors) {
Parameter[] parameters = constructor.getParameters();
logger.debug("paramer length : {}", parameters.length);
for (Parameter parameter : parameters) {
logger.debug("param type : {} {}", parameter.getType(), parameter.getName());
}
}
}
@Test
public void method() {
Class<Question> clazz = Question.class;
for (Method declaredMethod : clazz.getDeclaredMethods()) {
logger.debug("Method = {} {}", declaredMethod.getName(), declaredMethod.getReturnType());
Class[] parameterTypes = declaredMethod.getParameterTypes();
logger.debug("paramer length : {}", parameterTypes.length);
for (Class paramType : parameterTypes) {
logger.debug("param : {} {}", paramType, paramType.getName());
}
}
}
-
showClass()
-
clazz.getName()
: 클래스의 이름을 가져오는 메서드
-
-
fields()
-
getType()
: 필드의 타입을 가져온다. -
getName()
: 필드의 이름을 가져온다.
-
-
constructor()
-
getConstructors()
: 생성자 목록을 가져온다. -
getParameters()
: 생성자의 매개변수 목록을 가져온다.
-
요구사항 2 - test로 시작하는 메소드 실행
@Test
public void runner() throws Exception {
Class clazz = Junit3Test.class;
Method[] declaredMethods = clazz.getDeclaredMethods();
Arrays.stream(declaredMethods)
.filter(method -> method.getName().startsWith("test"))
.forEach(method -> {
try {
method.invoke(clazz.newInstance());
} catch (Exception e) {
throw new RuntimeException(e);
}
});
}
-
getDeclaredMethods()
: 메서드 목록을 가져온다-
getName()
: 메서드 이름을 가져와서 ‘test’로 시작하는지 확인한다. -
invoke()
: 메서드를 실행한다. 첫 번째 인자가 인스턴스, 두 번째부터는 메서드의 매개변수이다.
-
요구사항 3 - @Test 애노테이션 메소드 실행
@Test
public void run() throws Exception {
Class clazz = Junit4Test.class;
for (Method declaredMethod : clazz.getDeclaredMethods()) {
if(declaredMethod.isAnnotationPresent(MyTest.class)) {
declaredMethod.invoke(clazz.newInstance());
}
}
}
-
isAnnotationPresent()
: 메서드에 해당 Annotation이 붙어있는지 확인한다.
요구사항 4 - private field에 값 할당
@Test
@DisplayName("테스트 4 : private field에 값 할당")
public void privateFieldAccess() throws InstantiationException, IllegalAccessException {
Class<Student> clazz = Student.class;
logger.debug(clazz.getName());
Student student = clazz.newInstance();
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
if (field.getName().equals("name")) {
field.set(student, "재성");
} else {
field.set(student, 20);
}
}
assertThat(student.getName()).isEqualTo("재성");
assertThat(student.getAge()).isEqualTo(20);
}
-
setAccessible()
: true로 설정하면 접근 권한을 무시한다. false일 때는 원래의 접근 제한을 복원한다. -
set()
: field 값을 value로 설정한다.
요구사항 5 - 인자를 가진 생성자의 인스턴스 생성
@Test
@DisplayName("Test 5 : 인자를 가진 인스턴스 생성")
public void constructNew() {
try {
// Retrieve the Class object associated with the User class
Class<?> clazz = User.class;
// Initialize variables to hold the found constructor and the parameters
Constructor<?> foundConstructor = null;
Object[] params = new Object[]{"John Doe", 35};
// Iterate through all constructors of the User class
for (Constructor<?> constructor : clazz.getConstructors()) {
Class<?>[] parameterTypes = constructor.getParameterTypes();
// Check if the constructor has the correct parameter types
if (parameterTypes.length == 2
&& parameterTypes[0] == String.class
&& parameterTypes[1] == Integer.class) {
foundConstructor = constructor;
break;
}
}
// Check if a suitable constructor was found
if (foundConstructor != null) {
// Create a new instance of User using the found constructor
User user = (User) foundConstructor.newInstance(params);
// Verify the created object
assertEquals("John Doe", user.getName());
assertEquals(35, user.getAge());
} else {
throw new NoSuchMethodException("No matching constructor found");
}
} catch (Exception e) {
e.printStackTrace();
}
}
-
for (Constructor<?> constructor : clazz.getConstructors())
이 부분의 코드는
User
클래스의 모든 생성자를 순회하면서 특정한 조건을 만족하는 생성자를 찾는 과정입니다.
여기서는 생성자가String
과Integer
타입의 두 매개변수를 받아야 한다는 조건을 확인합니다. -
newInstance()
: 매개변수에 Object[]를 넣어도 자동으로 인스턴스가 생성된다.
보너스 미션 1 - 수행시간 측정
@Test
@DisplayName("보너스 미션 1 - 수행시간 측정")
void check_time() throws InstantiationException, IllegalAccessException, InvocationTargetException, InterruptedException {
Class clazz = Junit4Test.class;
for (Method declaredMethod : clazz.getDeclaredMethods()) {
if (declaredMethod.isAnnotationPresent(ElapsedTime.class)) {
long start = System.currentTimeMillis();
sleep(10);
declaredMethod.invoke(clazz.newInstance());
logger.debug("Time taken: {} ms", System.currentTimeMillis() - start);
}
}
}
홍은기
reflection을 처음 사용해보는데 이를 이용하면 런타임에 동적으로 메타데이터를 가져올 수 있고
이를 이용해서 정확한 테스트 코드를 구현할 수 있을 것 같다는 생각이 들었다.
요구사항 5에서 좀 더 단순한 방식으로 생성자의 형태를 확인할 수 있도록 고민해봐야겠다.
김현준
리플렉션이 많은 부분에서 사용하는데 특히 스프링에서 코드로 가끔 접했는데
이번 기회에 탐색해보면서 이를 이용한 다양한 아이디어를 떠올릴 수 있었다.
이번 미션에서도 실제 사용하고 있는데 더 동적으로 사용할 수 있을 것 같다!