Reflection

Java Reflection은 무엇인가?

  • Reflection은 java.lang.reflect 패키지에서 제공됩니다.

  • Class, Interface, Field, Method 등 프로그램의 구조를 런타임에 조사하고 수정할 수 있게 해주는 기능입니다.

기본 코드

  • Java Reflection API

    • Class: 클래스, 인터페이스, 배열 등의 메타데이터를 제공하는 클래스입니다.

      • 클래스 객체 얻기 (Class.forName(), .class, getClass())

      • 클래스 정보 접근 (클래스 이름, 수퍼 클래스, 인터페이스 등)

    • Field: 클래스 또는 인터페이스의 필드를 나타냅니다.

      • 필드 객체 얻기 (getField(), getDeclaredField())

      • 필드 값 읽기 및 쓰기 (get(), set()

      • 필드 정보 접근 (이름, 타입, 접근 제어자 등)

    • Method: 클래스 또는 인터페이스의 메서드를 나타냅니다.

      • 메서드 객체 얻기 (getMethod(), getDeclaredMethod())

      • 메서드 호출 (invoke() 메서드)

      • 메서드 정보 접근 (이름, 반환 타입, 매개 변수 등)

    • Constructor: 클래스의 생성자를 나타냅니다.

      • 생성자 객체 얻기 (getConstructor(), getDeclaredConstructor())

      • 인스턴스 생성 (newInstance() 메서드)

      • 생성자 정보 접근 (매개 변수 타입 등)

  • 실습 해보기

    • 클래스 메타데이터 출력

      • 주어진 클래스의 Method, Field, Contstructor 정보 출력

    • 동적 메서드 호출

      • 클래스의 메서드를 런타임에 호출 invoke(클래스, 메서드이름)

    • 필드 조작

      • 주어진 클래스의 private 필드를 런타임에 읽고 수정

사용사례 - 어떻게 사용되는가?

  • 프레임워크 구성 및 의존성 주입: Spring 같은 프레임워크에서 설정과 의존성 주입을 위해 Reflection을 사용하여 개발자가 명시적인 설정 없이 빈과 의존성을 정의하고 연결할 수 있습니다.

  • 직렬화 및 역직렬화: 객체를 바이트 스트림으로 변환하거나 그 반대로 변환하는 메커니즘에서 중요한 역할을 합니다. Jackson과 Gson 같은 라이브러리는 Reflection을 사용하여 Java 객체를 JSON으로 매핑하거나 그 반대로 매핑합니다.

  • 동적 클래스 로딩: 컴파일 시점에 알 수 없는 클래스를 런타임에 동적으로 로딩할 수 있습니다.

  • 디버깅 및 테스트: 디버깅과 테스트 프레임워크에서 런타임 중 객체를 검사하고 조작하여, 일반적인 테스트 케이스와 디버깅 도구를 만들 수 있게 합니다.

코드 예제: Reflection 이용한 스프링 의존성 주입

아래 예제는 @Autowired 를 이용해서 의존성을 주입받는 예시이다. 스프링에서 이렇게 작동하지 않을까 하고 만들어진 코드이다.

  • 조사에 따르면, 필드 주입 방식 Dependecny Injection에서 ReflectionUtils.makeAccessible 메서드를 사용하여 필드를 접근 가능하게 만들고, 의존성을 주입합니다. 이때, 내부적으로 setAccessible(true)를 호출하지만, Spring 프레임워크가 이를 안전하게 처리합니다.

    • 필드 주입 예시: @Autowired private Service service;

    • ReflectionUtils는 Spring 프레임워크에서 제공하는 유틸리티 클래스로, Reflection을 안전하게 사용할 수 있도록 도와주는 클래스입니다. 주로 필드 주입, 메서드 호출 등의 작업에서 Reflection을 사용할 때 유용하게 쓰입니다.

코드 예제: Reflection 이용한 JSON 직렬화 및 역직렬화

Reflection을 사용하여 JSON 직렬화 및 역직렬화를 구현한 이유는 런타임 시점에 객체의 필드와 그 타입을 동적으로 접근해야 하기 때문입니다.

JSON 직렬화와 역직렬화에서 Reflection을 사용하는 이유는 다음과 같은 상황에서 필요성이 있습니다:

  1. 동적 데이터 처리: JSON은 데이터의 값만을 포함하고 있지만, 이 값을 객체의 필드에 매핑하려면 해당 필드에 접근할 방법이 필요합니다. 특히, 객체의 필드명과 JSON의 키가 동적으로 결정될 수 있습니다. Reflection을 사용하면 런타임 시점에 객체의 필드를 동적으로 접근할 수 있어 이러한 동적 데이터 처리를 가능하게 합니다.

  2. 유지보수성 및 확장성: Reflection을 사용하면 코드를 객체의 구조 변경 없이 유지할 수 있습니다. 객체의 필드나 메서드가 추가되거나 변경되더라도 직렬화 및 역직렬화 로직을 자동으로 적용할 수 있습니다. 이는 코드의 유지보수성과 확장성을 높여줍니다.

  3. 다형성 처리: JSON으로 변환할 때 서브클래스의 객체를 슈퍼클래스 형태로 처리해야 할 경우 Reflection을 사용하여 다형성을 적절히 처리할 수 있습니다. 이는 객체의 실제 타입을 런타임에 파악하고 그에 맞게 JSON을 생성할 수 있도록 합니다.

Reflection 사용 시 주의할 점

  • 성능 문제 (런타임 오버헤드)

    • 속도 저하: Reflection은 정적인 코드에 비해 실행 시간이 길어질 수 있습니다. 예를 들어, 필드나 메서드에 접근할 때 직접 접근하는 것보다 Reflection을 사용하면 추가적인 검색과 메서드 호출이 필요하기 때문에 오버헤드가 발생할 수 있습니다.

    • 메모리 사용: Reflection을 사용하면 추가적인 메모리 할당이 필요할 수 있습니다. 특히 Reflection을 통해 동적으로 객체를 생성하거나 필드에 접근할 때, JVM은 추가적인 메타데이터를 관리해야 하므로 메모리 사용이 증가할 수 있습니다.

  • 타입 안정성 문제

    • 컴파일 시간에 타입 검사(type checking)가 이루어져서, 변수나 메서드 호출 등의 타입이 잘못 사용되는 경우를 사전에 방지합니다. 하지만 Reflection을 사용하면 컴파일 타임에 타입 검사가 이루어지지 않기 때문에, 런타임 시에 타입 관련 오류가 발생할 수 있는 가능성이 있습니다.

      • 타입 캐스팅 오류: ClassCastException

      • 메서드 시그니처 오류: NoSuchMethodException

      • 필드 접근 오류: IllegalArgumentException

    • 해결 방법

      • 실행 시점의 타입 검사: instanceof 연산자나 클래스 타입 비교 등을 활용하여 런타임 오류를 방지할 수 있습니다.

      • 예외 처리: try-catch 이용해 예외를 적절히 처리하여 애플리케이션의 안정성을 유지해야 합니다.

  • 보안 문제 및 캡슐화 위반 위험

    • Reflection은 Java의 보안 모델을 우회할 수 있는 도구로 악용될 수 있습니다. Reflection을 사용하면 접근 제어자(private, protected 등)를 무시할 수 있습니다. 그리고 Reflection을 사용하여 자바 시스템 클래스나 다른 민감한 클래스에 접근하거나, 실행 중인 코드를 동적으로 수정할 수 있습니다. 이는 애플리케이션의 권한 상승을 가능하게 할 수 있습니다.

    • Reflection을 사용하면 클래스의 private 필드나 메서드에도 접근할 수 있습니다. 이는 객체 지향 프로그래밍의 중요한 원칙인 캡슐화(encapsulation)를 위반할 수 있습니다. 캡슐화는 클래스 내부의 상세 구현을 외부에서 직접 접근하지 못하도록 보호하는 개념입니다. private 멤버에 접근할 필요가 있는 경우에도, 가능하면 Reflection을 사용하기보다는 getter/setter 메서드를 통해 접근하는 것이 좋습니다. 또한, 필요한 경우에만 접근할 수 있도록 접근 제어자를 적절히 설정하는 것이 중요합니다.

공부하면 좋은 자료

  • https://github.com/In-depth-Java-study/online/blob/main/src/main/java/yoochul/week02/Java%20Reflection%20Course.pdf

    • 발췌

      • 면접에서...

        • 채용자: "Do you know reflection?"

        • 지원자: "Yes, I do. You can use it to modify private final fields and call methods dynamically."

        • 채용자: "This interview is over. Thanks for applying and good luck for your future."

      • 답변 문제점

        • 적절한 사용 사례를 언급하며 Reflection을 사용해야 하는 상황과 그렇지 않은 상황을 구분하여 표현하지 않음

        • Reflection의 위험성을 언급하지 않고, 단순히 "private final 필드를 수정할 수 있다"는 식으로 언급한 것은 Reflection의 잠재적 위험에 대한 인식이 부족하다는 인상을 줄 수 있음.

      • 좋은 답변 예시

        • 지원자: "네, Reflection은 자바에서 클래스, 메소드, 필드의 런타임 검사와 수정을 가능하게 하는 강력한 도구입니다. private final 필드를 수정하거나 메소드를 동적으로 호출하는 데 사용될 수 있지만, 잠재적인 보안 위험과 성능 부하로 인해 신중하게 사용해야 합니다. 실제로는 Spring과 같은 프레임워크에서 의존성 주입과 AOP(Aspect-Oriented Programming)과 같은 동적 동작이 필요한 경우에 Reflection이 자주 사용됩니다. 이렇게 적절한 상황에 사용하면 좋지만 문제점이 있기도 합니다. Reflection은 정적인 코드보다 느리게 동작하기에 불필요한 사용은 성능 저하를 초래할 수 있습니다. 따라서 잘쓰면 약, 잘못쓰면 독이될 수 있기에 마주할 수 있는 문제에 대해 깊이 생각해보고 사용을 해야합니다."

    • 안에 나오는 내용

      • Introduction to Reflection

        • Class, Method, Constructor, Nested, Sealed, Records

      • Deep Reflection

        • Accessing private members, performance considerations

      • Arrays

        • Accessing elements, creating new

      • java.lang.invoke

        • MethodHandle and VarHandle

      • Reflection Introduction

        • Discovering object classes, calling methods, reading and writing fields, constructing new instances

      • Usefulness of Reflection

        • Frameworks (dependency injection, data serialization, dynamic configuration), serialization (ObjectOutputStream.writeObject()), and the power of minimal code

      • Dangers of Reflection

        • Code complexity, limitations of static code tools

      • Class Class

        • Identifying object classes, using getClass(), different class types (primitive types, interfaces, enums, annotations), methods for class type differentiation, and generic class handling

      • Class.forName()

        • Different forms of Class.forName() and usage examples

      • Methods in Classes

        • Retrieving methods with getMethod(), handling exceptions (NoSuchMethodException, InvocationTargetException, IllegalAccessException), dealing with nested classes, and modifiers

      • Better Exception Handling

        • Using ReflectiveOperationException and improving exception handling in reflection

      • Exercises

        • Practical exercises to reinforce the concepts learned

  • 흥미 있는 읽을 거리

Last updated