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. Class.forName("example.ExampleClass"):
forName
메소드를 사용하여 지정된 클래스 이름에 해당하는 Class 객체를 얻고 위 코드에서는 "example.ExampleClass"라는 클래스를 동적으로 로드합니다.
2. Constructor<?> constructor = clazz.getConstructor():
getConstructor
메소드를 사용하여 기본 생성자에 해당하는 Constructor 객체를 얻는다.
3. Object instance = constructor.newInstance():
newInstance
메소드를 사용하여 생성자를 호출하여 클래스의 새 인스턴스를 생성한다.
4. Method method = clazz.getMethod("exampleMethod", String.class):
getMethod
메소드를 사용하여 지정된 이름과 매개변수 유형에 해당하는 메소드를 얻고 여기서는 "exampleMethod"이라는 메소드를 가져온다.
5. method.invoke(instance, "Reflection Test"):
invoke
메소드를 사용하여 메소드를 호출하고 매개변수를 넘겨준다. 첫 번째 매개변수는 메소드가 호출될 객체(인스턴스)이며, 나머지는 메소드에 전달될 파라미터이다.
6. Field field = clazz.getDeclaredField("exampleField"):
getDeclaredField
메소드를 사용하여 클래스에 선언된 필드 중에서 지정된 이름에 해당하는 Field 객체를 얻는다.
7. field.setAccessible(true):
setAccessible
메소드를 사용하여 private 접근 제어자를 갖는 필드에 접근할 수 있도록 설정한다.
8. field.set(instance, "Reflection Field Value"):
set
메소드를 사용하여 필드에 값을 설정한다.
이 코드를 실행하면 "Reflection Test"와 "Reflection Field Value"가 차례로 출력될 것이라는 것을 추측해볼 수 있다. Reflection을 사용하여 동적으로 클래스를 조작하고 메소드 및 필드에 접근할 수 있다는 간단한 예제이다.
Reflection을 사용하는 프레임워크/라이브러리:
1. Spring Framework:
- Spring은 IoC (Inversion of Control) 컨테이너를 구현하는 데 Reflection을 활용한다. Bean 등록, 의존성 주입 등이 Reflection을 통해 동적으로 이루어집니다.
2. JUnit:
- JUnit은 테스트 케이스를 동적으로 찾아 실행하는 데 Reflection을 사용한다. 특히
@Test
어노테이션이 붙은 메소드를 찾아내어 실행한다.
- JUnit은 테스트 케이스를 동적으로 찾아 실행하는 데 Reflection을 사용한다. 특히
3. Hibernate:
- Hibernate는 객체 관계 매핑 (ORM)을 구현하는데 Reflection을 사용한다. 클래스와 데이터베이스 테이블 간의 매핑 정보를 동적으로 처리할 수 있다.
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());
}
}
}
Find every class with annotation: package.org.AnnotatedClass1
Find every class with annotation: package.org.AnnotatedClass2.AnnotatedClass2