리플렉션(Reflection)은 프로그래밍 중 실행 시간(runtime)에 클래스, 메서드, 필드 등 객체의 정보를 동적으로 확인하거나 조작할 수 있는 기능. 이를 통해 컴파일 시점에 알 수 없는 객체의 구조를 탐색하거나, 특정 동작을 수행할 수 있음.
Java에서 리플렉션은 java.lang.reflect 패키지에서 제공하는 API를 통해 사용되며, 클래스의 메타데이터(Class Metadata)에 접근하여 프로그램의 유연성을 높일 수 있음.
클래스 이름, 부모 클래스, 인터페이스 등 메타정보 확인 가능. Class 객체를 통해 접근 (Class.forName("클래스명"))
클래스 내 선언된 필드 및 메서드의 이름, 타입, 접근제어자 등의 정보를 확인 가능. getDeclaredFields(), getMethods() 등
컴파일 시점에 클래스 이름을 모르는 경우에도 동적으로 객체를 생성할 수 있음. : Class.newInstance()
특정 메서드를 실행 시간에 호출 가능 : Method.invoke(Object obj, Object... args)
객체의 필드 값을 동적으로 읽거나 수정할 수 있음. Field.set(Object obj, Object value)
Spring, Hibernate와 같은 프레임워크는 리플렉션을 활용해 의존성 주입(DI), 애노테이션 처리, 동적 프록시 생성 등을 수행. ex) Spring에서 @Autowired 필드 주입 처리.
: 런타임에 의존성을 동적으로 주입하는 메커니즘
컴파일 시점에 알 수 없는 클래스나 메서드와 상호작용이 필요할 때. ex) 플러그인 시스템에서 사용자가 추가한 기능을 동적으로 로드.
접근 제한된 필드나 메서드에 접근하여 테스트가 필요할 때. ex) setAccessible(true)를 통해 private 필드 테스트.
데이터 직렬화/역직렬화, JSON 변환 라이브러리(Jackson, Gson 등)에서 클래스 구조에 따라 동적 처리
: 직렬화와 역직렬화 시 private 필드에도 접근해야 하기 때문.
객체 상태를 분석하거나 메타정보를 로그로 출력할 때.
장점
import java.lang.reflect.Method;
public class ReflectionExample {
public static void main(String[] args) throws Exception {
// 클래스 로드
Class<?> clazz = Class.forName("java.lang.String");
// 메서드 조회
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
System.out.println("Method: " + method.getName());
}
// 동적 메서드 호출
String str = "Hello, World!";
Method substringMethod = clazz.getMethod("substring", int.class, int.class);
String result = (String) substringMethod.invoke(str, 0, 5);
System.out.println("Result: " + result); // 출력: Hello
}
}
리플렉션은 일반적인 개발에서는 가급적 지양하며, 아래의 경우에만 보통 사용.
리플렉션은 일반적인 업무에서는 자주 사용되지 않지만, 프레임워크 동작 원리를 이해하고 기본적인 활용 능력을 갖추는 것이 중요합니다. 특히, Java와 Spring 기반 백엔드 개발자에게는 다음과 같은 이유로 실습할 가치가 있습니다:
Spring 프레임워크 이해
테스트 코드 강화
setAccessible(true)를 이용한 private 필드 값 변경 및 검증.역직렬화 이해
동적 시스템 개발 능력
import java.lang.reflect.Method;
public class ReflectionExample {
public static void main(String[] args) throws Exception {
// 클래스 정보 탐색
Class<?> clazz = Class.forName("java.lang.String");
// 메서드 정보 출력
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
System.out.println("Method: " + method.getName());
}
}
}
import java.lang.reflect.Method;
class Sample {
public void sayHello() {
System.out.println("Hello, Reflection!");
}
}
public class ReflectionExample {
public static void main(String[] args) throws Exception {
// 클래스 로드
Class<?> clazz = Class.forName("Sample");
// 객체 생성
Object obj = clazz.getDeclaredConstructor().newInstance();
// 메서드 호출
Method method = clazz.getMethod("sayHello");
method.invoke(obj);
}
}
import java.lang.reflect.Method;
// 사용자 정의 애노테이션
@interface MyAnnotation {
String value();
}
class MyClass {
@MyAnnotation("Hello Annotation")
public void myMethod() {}
}
public class ReflectionExample {
public static void main(String[] args) throws Exception {
// 클래스 메타데이터 탐색
Class<?> clazz = MyClass.class;
// 메서드 애노테이션 정보 출력
Method method = clazz.getMethod("myMethod");
if (method.isAnnotationPresent(MyAnnotation.class)) {
MyAnnotation annotation = method.getAnnotation(MyAnnotation.class);
System.out.println("Annotation Value: " + annotation.value());
}
}
}
import java.lang.reflect.Field;
class Service {
public void serve() {
System.out.println("Service is serving!");
}
}
class Controller {
@Inject
private Service service;
public void execute() {
service.serve();
}
}
// 의존성 주입 애노테이션
@interface Inject {}
public class DIExample {
public static void main(String[] args) throws Exception {
Controller controller = new Controller();
// 의존성 주입
for (Field field : controller.getClass().getDeclaredFields()) {
if (field.isAnnotationPresent(Inject.class)) {
field.setAccessible(true);
field.set(controller, new Service());
}
}
// 메서드 실행
controller.execute(); // 출력: Service is serving!
}
}
Spring 애노테이션 동작 원리 이해
@Autowired, @Controller, @Service 애노테이션이 어떻게 동작하는지.AOP(Aspect-Oriented Programming) 학습
프레임워크 흉내내기
리플렉션은 신입 개발자가 직접 사용하는 일이 많지는 않지만, Spring과 같은 프레임워크의 동작 원리를 이해하고 문제 해결 능력을 기르는 데 필수적입니다. 위의 실습 예제를 통해 기본적인 개념과 활용 방법을 익히면 실무에서 직면할 프레임워크 이해에 큰 도움이 될 것입니다.