1월 29, 2024

Java Reflection 개념과 Relection을 사용한 코드 예제, 사용 프레임워크 또는 라이브러리

 Relection은 힙 영역에 로드되어 있는 Class type의 객체를 통해 메소드, 필드, 생성자를 접근 제어자와 관계 없이 사용할 수 있도록 지원하는 API이다.


컴파일 시점이 아닌 런타임 시점에 동적으로 특정 클래스의 정보를 추출할 수 있는 프로그래밍 기법이다. 이는 실행 중에 클래스의 정보를 가져오거나, 메소드를 호출하거나, 필드의 값을 읽거나 쓰는 등의 작업을 수행할 수 있게 해줍니다.


Reflection은 일반적으로 코드의 유연성이나 일부 특별한 상황에서만 사용해야 합니다. Reflection을 사용할 때는 주의가 필요하며, 컴파일 타임에 검증되는 타입 안정성을 잃을 수 있습니다.


import java.lang.reflect.*;

public class ReflectionExample {
public static void main(String[] args) throws ClassNotFoundException,
 NoSuchMethodException, IllegalAccessException, 
InvocationTargetException, InstantiationException, NoSuchFieldException {

// 클래스 이름으로 Class 객체 얻기
Class<?> clazz = Class.forName("example.ExampleClass");

// 생성자 호출
Constructor<?> constructor = clazz.getConstructor();
Object instance = constructor.newInstance();

// 메소드 호출
Method method = clazz.getMethod("exampleMethod", String.class);
method.invoke(instance, "Reflection Test");

// 필드에 값 설정 및 읽기
Field field = clazz.getDeclaredField("exampleField");
field.setAccessible(true);
field.set(instance, "Reflection Field Value");
System.out.println(field.get(instance));
}
}

class MyClass {
private String myField;

public void myMethod(String message) {
System.out.println(message);
}
}

Reflection을 활용한 간단한 코드 예제이다.



위 코드는 Java Reflection을 사용하여 클래스를 동적으로 로드하고 생성자 호출, 메소드 호출, 필드에 값 설정 및 읽기를 수행하는 간단한 예제이다. 위 코드의 몇 가지 핵심적인 개념을 살펴보자.

  1. 1. Class.forName("example.ExampleClass"):

    • forName 메소드를 사용하여 지정된 클래스 이름에 해당하는 Class 객체를 얻고 위 코드에서는 "example.ExampleClass"라는 클래스를 동적으로 로드합니다.
  2. 2. Constructor<?> constructor = clazz.getConstructor():

    • getConstructor 메소드를 사용하여 기본 생성자에 해당하는 Constructor 객체를 얻는다.
  3. 3. Object instance = constructor.newInstance():

    • newInstance 메소드를 사용하여 생성자를 호출하여 클래스의 새 인스턴스를 생성한다.
  4. 4. Method method = clazz.getMethod("exampleMethod", String.class):

    • getMethod 메소드를 사용하여 지정된 이름과 매개변수 유형에 해당하는 메소드를 얻고 여기서는 "exampleMethod"이라는 메소드를 가져온다.
  5. 5. method.invoke(instance, "Reflection Test"):

    • invoke 메소드를 사용하여 메소드를 호출하고 매개변수를 넘겨준다. 첫 번째 매개변수는 메소드가 호출될 객체(인스턴스)이며, 나머지는 메소드에 전달될 파라미터이다.
  6. 6. Field field = clazz.getDeclaredField("exampleField"):

    • getDeclaredField 메소드를 사용하여 클래스에 선언된 필드 중에서 지정된 이름에 해당하는 Field 객체를 얻는다.
  7. 7. field.setAccessible(true):

    • setAccessible 메소드를 사용하여 private 접근 제어자를 갖는 필드에 접근할 수 있도록 설정한다.
  8. 8. field.set(instance, "Reflection Field Value"):

    • set 메소드를 사용하여 필드에 값을 설정한다.

이 코드를 실행하면 "Reflection Test"와 "Reflection Field Value"가 차례로 출력될 것이라는 것을 추측해볼 수 있다. Reflection을 사용하여 동적으로 클래스를 조작하고 메소드 및 필드에 접근할 수 있다는 간단한 예제이다.


Reflection을 사용하는 프레임워크/라이브러리:

  1. 1. Spring Framework:

    • Spring은 IoC (Inversion of Control) 컨테이너를 구현하는 데 Reflection을 활용한다. Bean 등록, 의존성 주입 등이 Reflection을 통해 동적으로 이루어집니다.
  2. 2. JUnit:

    • JUnit은 테스트 케이스를 동적으로 찾아 실행하는 데 Reflection을 사용한다. 특히 @Test 어노테이션이 붙은 메소드를 찾아내어 실행한다.
  3. 3. Hibernate:

    • Hibernate는 객체 관계 매핑 (ORM)을 구현하는데 Reflection을 사용한다. 클래스와 데이터베이스 테이블 간의 매핑 정보를 동적으로 처리할 수 있다.

특히 2번 JUnit에서 볼 수 있듯이 Reflection은 @Annotation이 붙은 메소드나 클래스 들을 동적으로 찾아낼 수 있는데, 관련 예제도 살펴보자.



import org.junit.jupiter.api.Test;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface MyCustomAnnotation {
}

@MyCustomAnnotation
class AnnotatedClass1 {
}

@MyCustomAnnotation
class AnnotatedClass2 {
}

class NotAnnotatedClass {
}

public class AnnotationTestClass {

@Test
public void findAnnotatedClasses() {
// 패키지명 설정
String packageName = "package.org";

// 패키지 내 모든 클래스를 가져오기
Reflections reflections = new Reflections(packageName);
Set<Class<?>> classes = reflections.getTypesAnnotatedWith(MyCustomAnnotation.class);

// 결과 출력
for (Class<?> clazz : classes) {
System.out.println("Find every class with annotation: " + clazz.getName());
}
}
}

위 코드처럼 작성을 하면
package.org 라는 package 경로 아래 MyCustomAnnotation이라는 Annotation이 붙어있는 모든 class를 찾을 수 있다.

@MyCustomAnnotation이 붙은 Class는
AnnotatedClass1과 AnnotatedClass2이므로

콘솔에 출력된 로그는 아래와 같을 것이다.
Find every class with annotation: package.org.AnnotatedClass1
Find every
class with annotation: package.org.AnnotatedClass2.AnnotatedClass2

이런 식으로 오늘은 Reflection을 사용한 코드 예제들을 알아보았다.