Reflection은 Java에서 런타임 시 클래스, 메서드, 필드 등에 동적으로 접근할 수 있는 강력한 기능입니다. 이를 통해 클래스의 구조를 분석하고 객체의 필드나 메서드에 접근할 수 있으며, 프레임워크나 라이브러리에서도 많이 사용되고 있습니다. 이번 글에서는 Java Reflection의 기본 개념부터 실제 활용 예까지 순차적으로 설명드리겠습니다.

 

 

Reflection 기본 동작원리

 

 Reflection은 Java의 java.lang.reflect 패키지에 포함된 기능으로, 클래스나 객체의 메타데이터에 접근하여 필드, 메서드, 생성자 등의 정보를 런타임에 탐색하거나 조작할 수 있는 기법입니다.

 특히 컴파일 시점에는 알 수 없는 클래스나 객체의 구조를 동적으로 파악하고, 메서드 호출, 필드 수정 등의 작업을 할 수 있어 동적 프로그램 설계에 큰 유연성을 제공합니다.

 

 

1. Class 객체를 통해 클래스 정보에 접근

Reflection의 첫 번째 단계는 Class 객체를 통해 해당 클래스의 메타데이터를 가져오는 것입니다. Class<?> 객체는 해당 클래스의 모든 정보를 담고 있습니다. Class.forName()이나 클래스명.class와 같은 방식으로 Class 객체를 얻을 수 있습니다. 이 객체를 통해 해당 클래스의 필드, 메서드, 생성자 등의 정보를 가져옵니다.

 

Class<?> clazz = User.class; // User 클래스의 Class 객체를 얻음
Field[] fields = clazz.getDeclaredFields(); // 해당 클래스의 필드 정보들을 배열로 가져옴

 

 코드에서 사용된 User.class는 Class<User>라는 의미로, User 클래스의 메타데이터를 JVM에서 가져와 Class 객체로 표현한 것입니다. 이 Class 객체는 런타임에 Reflection API를 사용해 User 클래스의 필드, 메서드 등의 정보를 동적으로 탐색하는 데 사용됩니다.

 

 

2. 필드 값 접근 및 가져오기

List<Field> userFields = new ArrayList<>(Arrays.asList(clazz.getDeclaredFields()));

 

 이 메서드가 호출되면 JVM은 User 클래스의 메타데이터 테이블에서 필드 정보를 조회하여 Field[] 객체에 담아 반환합니다. 여기서 중요한 부분은 컴파일 시점에는 이 필드들이 무엇인지 알 수 없지만, 런타임 시에는 클래스 로더(Class Loader)가 해당 정보를 가져와 처리한다는 점 입니다.

 

field.setAccessible(true);

이 메서드가 호출되면, JVM은 보안 관리자(Security Manager)를 확인하여 해당 필드에 대한 접근을 허용할지를 결정합니다. 만약 보안 관리자가 해당 동작을 허용하지 않으면 SecurityException이 발생할 수 있습니다. 그러나 대부분의 경우, 애플리케이션의 보안 설정이 기본값인 경우에는 이 제한을 우회하여 필드에 접근할 수 있습니다.


내부적으로 setAccessible(true)는 JVM에게 필드에 대한 접근 권한을 강제로 부여하는 역할을 합니다. 이 과정은 JVM의 native 메서드로 처리되며, 이로 인해 비공개(private) 필드에도 접근할 수 있게 됩니다. 이 과정은 JVM 레벨에서 접근 권한을 변경하는 것이기 때문에, 자바의 private 제어자로 선언된 필드나 메서드라도 프로그램에서 자유롭게 다룰 수 있게 됩니다.

 

Object fieldValue = field.get(user);

실제 객체에서 필드 값을 읽어오는 작업입니다. 여기서 중요한 점은 Field 객체가 필드의 이름과 타입 같은 정보만을 가지고 있을 뿐, 실제로는 해당 객체의 메모리 위치에서 값을 가져오는 역할을 합니다.

field.get(Object obj)는 내부적으로 native 메서드로 구현되어 있으며, JVM이 해당 객체의 필드 메모리 주소를 찾아 그 값을 반환하는 방식입니다. 예를 들어 User 객체에서 username 필드를 가져오려면, JVM은 heap 영역에 저장된 user 객체의 메모리 위치를 조회하고, 그 안에서 username 필드가 위치한 메모리 주소로 이동해 값을 가져옵니다.

JVM 내부적으로는 객체의 클래스 정보를 어떻게 관리하고 구성되어 있는지를 알고 있기 때문에, Field 객체가 메모리 상에서 해당 필드의 위치를 찾아 값을 추출할 수 있습니다.

 

*JVM 스펙 및 메모리 구조 : https://docs.oracle.com/javase/specs/jvms/se17/html/

 

String fieldType = field.getType().getSimpleName();

이 부분에서 field.getType()은 필드의 데이터 타입을 반환합니다. 이때도 Field 객체는 해당 필드가 어떤 타입인지에 대한 정보를 포함하고 있습니다. 예를 들어, 필드가 String이라면 field.getType()은 String.class를 반환하고, int라면 int.class를 반환합니다.

JVM은 모든 필드의 타입 정보를 클래스 로더가 로드될 때 Method Area에 저장해두기 때문에, 런타임에도 이 정보를 바로 조회할 수 있습니다. 이를 통해, 우리는 런타임 시에 필드의 타입을 유연하게 처리할 수 있게 됩니다.

 

 

3. 메서드와 생성자 호출하기

Reflection을 이용하면 클래스의 메서드와 생성자에도 접근할 수 있습니다. 메서드를 호출할 때는 Method.invoke()를 사용하며, 생성자를 호출해 새로운 객체를 동적으로 생성할 수도 있습니다.

Method method = clazz.getDeclaredMethod("method4", int.class);
method.setAccessible(true);
int result = (int) method.invoke(user, 10);
System.out.println("결과: " + result);

 

위 코드에서 method는 int 파라미터를 가진 메서드이며, invoke()를 통해 동적으로 호출됩니다. 이와 같이 Reflection은 private 메서드나 생성자도 호출할 수 있게 해주어 유연한 설계를 가능하게 해줍니다.

 

 

4. Reflection의 장단점


장점:

1. 동적 프로그래밍:

컴파일 시점이 아닌 런타임에 클래스 정보를 탐색하고 조작할 수 있어 유연한 코드 작성이 가능합니다.
2. 프레임워크:

많은 프레임워크(Spring, Hibernate 등)에서 동적 객체 생성, 의존성 주입 등에 Reflection을 사용합니다.


단점:

1. 성능 저하:

Reflection은 런타임 시 클래스 메타데이터 테이블을 조회하고, 추가적인 작업을 해야 하므로 성능 저하가 발생할 수 있습니다. 특히 반복적으로 사용하는 경우 성능에 큰 영향을 미칠 수 있습니다.
2. 캡슐화 위반:

pirvate 필드와 메서드에도 접근이 가능하므로, 잘못 사용하면 객체지향 설계 원칙인 캡슐화가 깨질 수 있습니다.

 

 

5. 정리

 Java Reflection은 동적 프로그래밍의 핵심 도구로서, 런타임에 클래스나 메서드 정보를 자유롭게 탐색하고 조작할 수 있는 기능을 제공합니다.

 이 같은 특성을 활용하면 웹 서비스에서 WAS 구동 이후에도 환경설정 값을 런타임에 변경할 수 있는 코드도 구현할 수 있습니다. 또한 프레임워크(Spring, Hibernate 등)의 의존성 주입과 같은 코어개념에 Reflection이 사용되었습니다.

 Reflection을 사용하면 위와 같은 유연한 코드 설계를 가능하게 해줍니다. 다만 사용할때는 성능 저하와 캡슐화 위반 같은 부분을 잘 고려해서 사용하는게 중요합니다.

 

감사합니다.

반응형
  • 네이버 블러그 공유하기
  • 네이버 밴드에 공유하기
  • 페이스북 공유하기
  • 카카오스토리 공유하기