본문 바로가기
Spring

리플렉션

by xogns93 2024. 10. 8.

리플렉션(Reflection)은 자바에서 런타임에 클래스, 메서드, 필드 등의 정보를 동적으로 조사하고 조작할 수 있는 기능을 제공하는 API입니다. 즉, 컴파일 시점이 아닌 실행 시점에 객체의 구조를 분석하고, 그 구조에 따라 메서드를 호출하거나 필드 값을 변경하는 등의 작업을 수행할 수 있습니다.

💡 리플렉션의 핵심 개념

리플렉션을 사용하면 다음과 같은 작업을 할 수 있습니다:

  • 클래스의 메타데이터(클래스 이름, 메서드, 필드 등)를 런타임에 동적으로 얻어낼 수 있습니다.
  • 클래스의 필드 값을 읽거나 변경할 수 있습니다.
  • 메서드를 호출하거나 생성자를 호출해 객체를 동적으로 생성할 수 있습니다.
  • 애노테이션과 같은 메타데이터를 조사할 수 있습니다.

💡 리플렉션 API의 주요 기능

  1. 클래스 정보 가져오기:

    • Class<?> 객체를 통해 클래스 정보를 얻어낼 수 있습니다.
    • Class.forName("클래스이름") 또는 object.getClass()로 클래스 객체를 가져옵니다.
    Class<?> clazz = Class.forName("com.example.MyClass");
  2. 필드, 메서드, 생성자 정보 확인:

    • 클래스에 선언된 필드, 메서드, 생성자의 정보를 런타임에 가져올 수 있습니다.
    Field[] fields = clazz.getDeclaredFields();   // 클래스의 필드 목록
    Method[] methods = clazz.getDeclaredMethods(); // 클래스의 메서드 목록
    Constructor<?>[] constructors = clazz.getDeclaredConstructors(); // 클래스의 생성자 목록
  3. 필드 값 읽기 및 변경:

    • 객체의 필드 값을 동적으로 읽거나 수정할 수 있습니다. private 필드도 접근 제어를 우회하여 수정할 수 있습니다.
    Field field = clazz.getDeclaredField("name");
    field.setAccessible(true);  // private 필드 접근 허용
    String value = (String) field.get(instance);  // 필드 값 가져오기
    field.set(instance, "newName");  // 필드 값 설정
  4. 메서드 호출:

    • 리플렉션을 사용해 특정 객체의 메서드를 호출할 수 있습니다.
    Method method = clazz.getDeclaredMethod("sayHello", String.class);
    method.setAccessible(true);  // private 메서드 접근 허용
    method.invoke(instance, "World");  // 메서드 실행
  5. 생성자 호출 및 객체 생성:

    • 리플렉션을 통해 클래스의 생성자를 호출하여 객체를 동적으로 생성할 수 있습니다.
    Constructor<?> constructor = clazz.getDeclaredConstructor(String.class);
    Object newInstance = constructor.newInstance("SomeName");
  6. 애노테이션 정보 가져오기:

    • 클래스나 메서드에 선언된 애노테이션 정보를 런타임에 읽을 수 있습니다.
    if (clazz.isAnnotationPresent(MyAnnotation.class)) {
        // 애노테이션이 있으면 처리
    }

💡 리플렉션의 장점

  1. 유연성: 코드 작성 시점에 정의되지 않은 클래스나 메서드에 동적으로 접근할 수 있어, 유연한 구조를 설계할 수 있습니다.
  2. 프레임워크에서의 활용: 리플렉션은 Spring, Hibernate, JUnit 같은 프레임워크에서 핵심적인 역할을 합니다. 예를 들어, 스프링의 의존성 주입은 리플렉션을 통해 객체를 동적으로 생성하고 주입합니다.
  3. 동적 객체 생성: 런타임에 동적으로 객체를 생성하고 메서드를 호출할 수 있어, 컴파일 시점에 알 수 없는 클래스에 대한 작업도 가능합니다.

💡 리플렉션의 단점

  1. 성능 문제: 리플렉션은 일반적인 메서드 호출보다 성능이 떨어집니다. 런타임에 메타데이터를 분석하고 동적으로 메서드를 호출하는 과정에서 추가적인 비용이 발생합니다.
  2. 안정성 문제: 컴파일 시점에 타입이 검증되지 않기 때문에, 잘못된 메서드 호출이나 필드 접근으로 인해 런타임 오류가 발생할 가능성이 있습니다.
  3. 캡슐화 위반: 리플렉션은 private 메서드나 필드에도 접근할 수 있기 때문에, 객체지향 프로그래밍의 중요한 원칙인 캡슐화(encapsulation)를 위반할 수 있습니다.

💡 리플렉션의 실제 사용 예시

public class Person {
    private String name;

    public Person(String name) {
        this.name = name;
    }

    private void sayHello(String message) {
        System.out.println(name + " says: " + message);
    }
}

public class ReflectionDemo {
    public static void main(String[] args) throws Exception {
        // 클래스 객체를 동적으로 가져오기
        Class<?> clazz = Class.forName("Person");

        // 생성자를 통해 객체 생성
        Constructor<?> constructor = clazz.getConstructor(String.class);
        Object person = constructor.newInstance("John");

        // private 메서드 호출
        Method method = clazz.getDeclaredMethod("sayHello", String.class);
        method.setAccessible(true);  // private 메서드 접근 허용
        method.invoke(person, "Hello!");  // 메서드 호출
    }
}

출력:

John says: Hello!

💡 리플렉션이 사용되는 곳

  • Spring Framework: 의존성 주입, AOP(Aspect Oriented Programming) 등에 리플렉션이 사용됩니다.
  • JUnit: 테스트 프레임워크에서 메서드나 클래스의 동적 실행에 리플렉션을 활용합니다.
  • Hibernate: ORM에서 객체와 데이터베이스 테이블 간의 매핑을 처리하기 위해 리플렉션을 사용합니다.

결론

리플렉션은 런타임에 동적으로 클래스와 객체를 다룰 수 있는 강력한 도구로, 프레임워크나 라이브러리 개발에서 필수적입니다. 하지만 성능 저하와 캡슐화 위반 같은 단점도 있기 때문에, 필요할 때 적절히 사용하는 것이 중요합니다.