Skip to main content

Command Palette

Search for a command to run...

Reflection

Published
3 min read

Refleaction 이란?

자바의 리플렉션(Reflection)은 클래스나 멤버변수, 메서드, 생성자에 대한 정보를 가져오거나 수정하고, 런타임 시에 동적으로 객체를 생성하거나 메서드를 호출하는 등의 작업을 할 수 있게 해주는 자바 API이다.

Spring 프레임워크에서는 리플렉션을 의존성 주입(Dependency Injection)같은 곳에서 리플렉션 기능을 활용한다. Spring 컨테이너는 Bean 객체를 생성할 때 해당 클래스의 정보를 리플렉션을 통해 분석하고, 이를 통해 객체를 생성하며 필요한 의존성을 주입한다.

그래서 결론을 먼저 말하자면 라이브러리 개발을 하지 않는 일반 개발자 특히, 주니어들은 이 리플렉션을 크게 다룰일이 없을 것이고 보안과 성능에도 영향을 끼치는 요소이니 함부로 쓰지 말자.

구조

원본 클래스 코드가 있고, 컴파일 과정을 거쳐 런타임 도중 클래스 메타데이터가 Method Area에 저장된다. 리플렉션은 이곳에 저장된 데이터를 통해 클래스를 검사하고 조작하는 기능을 하는 것이다.

사용 예시

reflection 통해 클래스에 접근하는 3가지 방법

1. Class.forName():

try {
    Class<?> clazz = Class.forName("java.util.ArrayList");
    System.out.println("Class Name: " + clazz.getName());
} catch (ClassNotFoundException e) {
    e.printStackTrace();
}

2. .class 접근자:

Class<?> clazz = java.util.ArrayList.class;
System.out.println("Class Name: " + clazz.getName());

3. getClass() 메소드:

java.util.ArrayList arrayList = new java.util.ArrayList();
Class<?> clazz = arrayList.getClass();
System.out.println("Class Name: " + clazz.getName());

여기서 Class.forName() 을 통해 실제 reflection을 사용하는 예시코드를 보자

원본 클래스

public class ReflectionExample {
    public int publicField = 10;
    private int privateField = 20;

    public int addNumbers(int a, int b) {
        return a + b;
    }
}

reflection 사용

public class Main {
    public static void main(String[] args) {
        try {
            // 클래스 로딩
            Class<?> clazz = Class.forName("ReflectionExample");

            // 인스턴스 생성
            Object instance = clazz.getDeclaredConstructor().newInstance();

            // public 필드 값 가져오기
            Field publicField = clazz.getField("publicField");
            int publicFieldValue = (int) publicField.get(instance);
            System.out.println("The value of the public field is: " + publicFieldValue);

            // private 필드 값 가져오기
            Field privateField = clazz.getDeclaredField("privateField");
            privateField.setAccessible(true); // 접근 가능하도록 설정
            int privateFieldValue = (int) privateField.get(instance);
            System.out.println("The value of the private field is: " + privateFieldValue);

            // 메소드 호출
            Method method = clazz.getMethod("addNumbers", int.class, int.class);
            int result = (int) method.invoke(instance, 5, 3);
            System.out.println("The result of the method call is: " + result);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

이와 같이 클래스나 인터페이스에 대한 정보에 직접 접근하여 파악, 사용이 가능하다.

이러한 방식으로 사용되는 리플렉션은 주로 두 가지 주요한 목적으로 사용된다.

  • 런타임시 동적 프로그래밍: 리플렉션을 사용하면 프로그램이 실행 중인 시점에서 동적으로 클래스를 로드하고, 인스턴스를 생성하며, 메서드를 호출하거나 필드 값을 가져오는 등의 작업을 수행할 수 있다. 즉, 컴파일 시점에는 알 수 없는 클래스나 메서드에 대해 런타임 시점에서 접근할 수 있게 된다. 이는 특히 플러그인 시스템이나, 프레임워크, 개발 도구 등에서 유용하게 사용된다.

  • 유연성과 재사용성 증가: 리플렉션을 통해 클래스나 메서드를 직접적으로 참조하지 않고도 사용할 수 있기 때문에, 코드의 유연성과 재사용성이 증가한다. 예를 들어, Spring 프레임워크에서는 리플렉션을 이용해 의존성 주입을 수행함으로써 코드의 유연성을 높이고, 클래스 간의 결합도를 낮춘다.

하지만 리플렉션은 성능 오버헤드가 있고, 코드의 복잡성을 증가시키며, 잘못 사용하면 내부 요소에 그냥 접근이 가능하기에 보안 문제를 일으킬 수 있으므로, 반드시 필요한 경우에만 사용해야 한다. 가능하다면 설계 단계에서 인터페이스나 상속 등을 활용하여 문제를 해결하는 것이 더 바람직하다.

Techtalk

Part 1 of 15

주간 테크 영상을 보고 논의 후 정리하여 포스팅하는 공간

Up next

Spring Bean

빈 빙 돌아가는 회전 목마처럼