포스트

JAVA (19) - 리플렉션 기초

JAVA (19) - 리플렉션 기초

1. 개요

자바의 리플렉션(Reflection)은 구체적인 클래스 타입을 알지 못하더라도 런타임 시점에 클래스의 메타데이터(클래스 구조, 메서드, 필드, 생성자 등)에 접근하고 이를 조작할 수 있게 해주는 기능이다. 작성 시점에는 알 수 없는 클래스를 동적으로 로딩하여 사용할 수 있게 해주며, Spring Framework, ORM(JPA/Hibernate), 테스트 프레임워크(JUnit) 등 자바 생태계의 다양한 프레임워크와 라이브러리에서 핵심적인 기반 기술로 활용된다.

2. Class 객체 획득하기

리플렉션을 사용하기 위해서는 가장 먼저 해당 클래스의 정보를 담고 있는 Class 객체를 얻어야 한다. 대표적으로 세 가지 방법이 존재한다.

  1. 클래스명.class: 클래스 리터럴을 직접 사용하는 방식
  2. 객체.getClass(): 이미 생성된 객체 인스턴스를 통해 호출하는 방식
  3. Class.forName("패키지명을 포함한 클래스명"): 문자열 형태의 클래스 이름으로 런타임에 로딩하는 방식 (클래스가 존재하지 않을 경우 ClassNotFoundException 발생)
1
2
3
4
5
6
7
8
9
10
// 1. 클래스 리터럴
Class<User> clazz1 = User.class;

// 2. getClass()
User user = new User();
Class<? extends User> clazz2 = user.getClass();

// 3. Class.forName()
Class<?> clazz3 = Class.forName("com.example.User");

3. 메타데이터 조회 및 객체 조작

획득한 Class 객체를 활용하면 클래스 내부에 정의된 요소들을 탐색하고 동적으로 제어할 수 있다.

(1) 생성자 접근 및 인스턴스 생성

1
2
3
4
// 파라미터가 없는 기본 생성자 획득
Constructor<?> constructor = clazz1.getDeclaredConstructor();
User userInstance = (User) constructor.newInstance();

(2) 필드 접근 및 값 수정

getDeclaredField()를 사용하면 private으로 선언된 필드에도 접근할 수 있다. 접근 제어자를 우회하기 위해서는 setAccessible(true)를 호출해야 한다.

1
2
3
4
5
6
Field nameField = clazz1.getDeclaredField("name");
nameField.setAccessible(true); // private 접근 허용

// 필드 값 변경
nameField.set(userInstance, "홍길동");

(3) 메서드 동적 호출

1
2
3
4
5
6
7
// 매개변수가 없는 메서드 획득
Method printMethod = clazz1.getDeclaredMethod("printInfo");
printMethod.setAccessible(true);

// 메서드 실행 (invoke)
printMethod.invoke(userInstance);

4. 리플렉션의 주요 활용 사례

일반적인 비즈니스 로직 개발에서 리플렉션을 직접 사용하는 경우는 드물다. 하지만 프레임워크 단에서는 유연한 구조를 위해 활발히 쓰인다.

  • Spring Framework: @Autowired를 통한 의존성 주입(DI), @Controller 등 애노테이션 기반 컴포넌트 스캔 과정에서 동적으로 객체를 생성하고 결합할 때 사용한다.
  • JSON 직렬화/역직렬화 (Jackson, Gson): JSON 데이터를 자바 객체로 변환할 때, 기본 생성자를 생성하고 필드 값을 리플렉션으로 매핑한다.
  • JUnit 테스트 프레임워크: @Test 애노테이션이 붙은 메서드를 동적으로 탐색하여 실행한다.

쉽고 확실하게 직관적으로 이해할 수 있는 현실 세계 비유 예시간단한 코드 예시로 설명해 준다.


코드 예시: 리플렉션이 빛을 발하는 순간

우리가 웹 서버를 만들 때 @AutoWired@Test 같은 애노테이션을 붙여두면 알아서 실행되는 원리가 바로 이것이다.

내가 만든 클래스 (미래에 만들어질 코드)

1
2
3
4
5
6
7
8
public class Robot {
    private String secretKey = "1234-5678"; // private 변수

    public void powerOn() {
        System.out.println("로봇 전원 ON!");
    }
}

프레임워크 개발자가 작성한 리플렉션 코드

프레임워크를 만드는 사람은 내가 Robot이라는 클래스를 만들지, Car라는 클래스를 만들지 코드 작성 시점에는 전혀 모른다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class FrameworkEngine {
    public void run(Object obj) throws Exception {
        // 1. 들어온 객체의 클래스 정보를 런타임에 엿본다 (X-ray 스캔)
        Class<?> clazz = obj.getClass();
        
        System.out.println("클래스 이름: " + clazz.getName()); // "Robot" 출력

        // 2. private 필드에 접근해서 값을 강제로 꺼내온다
        Field field = clazz.getDeclaredField("secretKey");
        field.setAccessible(true); // private 접근 허용!
        String key = (String) field.get(obj);
        System.out.println("비밀키 추출: " + key); // "1234-5678" 출력

        // 3. powerOn 메서드를 찾아서 실행시킨다
        Method method = clazz.getDeclaredMethod("powerOn");
        method.invoke(obj); // "로봇 전원 ON!" 출력
    }
}

3줄 요약

  1. 원래 자바는: 코드를 짤 때 클래스 이름이나 메서드 이름을 미리 다 알고 적어야만 호출할 수 있다.
  2. 리플렉션은: 코드를 짤 때는 몰랐더라도, 프로그램이 실행 중일 때(Runtime) 객체를 스캔해서 “너 무슨 변수 갖고 있어? 무슨 메서드 있어?” 하고 물어보고 동적으로 실행하는 기술이다.
  3. 어디서 쓰이냐면: 내가 무슨 클래스를 만들지 미리 알 수 없는 스프링(Spring) 같은 프레임워크가 내 코드를 대신 실행해 줄 때 핵심적으로 사용한다.

5. 리플렉션 사용 시 주의점 및 단점

리플렉션은 강력한 유연성을 제공하지만 분명한 단점이 존재하므로 신중하게 사용해야 한다.

  1. 성능 오버헤드: 컴파일 타임 최적화를 받을 수 없고 런타임에 동적으로 분석하므로 일반적인 메서드 호출보다 실행 속도가 느리다.
  2. 타입 안전성 상실: 컴파일 시점에 타입 검사를 할 수 없어, 잘못된 매개변수나 메서드 호출 시 런타임 예외(NoSuchMethodException, IllegalAccessException 등)가 발생한다.
  3. 캡슐화 파괴: setAccessible(true)를 통해 private 멤버에 직접 접근이 가능해지므로, 객체 지향의 캡슐화 원칙을 저해할 수 있다.

6. 정리

자바 리플렉션은 런타임에 클래스의 정보에 접근하여 동적으로 객체를 생성하고 조작할 수 있는 강력한 기능이다. 프레임워크와 라이브러리 개발에 있어서 필수적인 유연성을 제공하지만, 성능 저하, 타입 안전성 결여, 캡슐화 파괴라는 위험 요소도 함께 동반된다. 따라서 일반적인 애플리케이션 개발 시에는 표준적인 정적 타입 명시 방식을 우선으로 하고, 리플렉션은 필요한 상황에 한해 제약적으로 활용하는 것이 바람직하다.

7. 요약

  1. 정의: 코드 작성 시점에는 몰랐던 클래스의 정보(메서드, 변수 등)를 프로그램 실행 중에 동적으로 조사하고 다루는 기술이다.
  2. 장점: 어떤 클래스가 들어올지 모르는 상황(프레임워크, 라이브러리)에서 엄청난 유연성을 준다.
  3. 단점: 실행 중에 일일이 분석하므로 속도가 살짝 느리고, private까지 강제로 열 수 있어 안전성/캡슐화가 깨질 수 있다.
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.