JAVA - 리플렉션(Reflection)

 


1. 리플렉션(Reflection)

자바의 리플렉션은 런타임 시점클래스의 내부 구조(클래스, 메서드, 필드, 생성자 등)에 접근하고 조작할 수 있게 해주는 기능입니다.

 

ExampleData exampleData = new ExampleData();
// 일반적인 방법으로 메소드 호출
exampleData.hello();


// Reflection
Class<?> dataClass = exampleData.getClass();
        
String methodName = "hello"; // 이부분에서 동적으로 수정가능
Method helloMethod = dataClass.getDeclaredMethod(methodName);
helloMethod.invoke(exampleData);

위 코드는 자바 리플렉션을 사용하여 특정 메서드(hello)를 런타임에 동적으로 호출하는 예제입니다. 이처럼 사용자의 입력이나 특정 조건에 따라 메서드 이름을 문자열로 지정하고 실행할 수 있게 하는 것이 리플렉션의 기능입니다.

 

 


 

2. Reflection API

 

2.1) java.lang.Class

자바의 모든 클래스는 로딩될 때, 클래스의 메타데이터를 담고 있는 Class 객체로 표현됩니다. 이 객체는 클래스 이름, 패키지, 접근 제어자, 상속 관계, 구현된 인터페이스, 그리고 선언된 필드와 메서드 등의 정보를 포함합니다.

 

- 클래스 정보를 얻는 3가지 방법

1. 클래스 리터럴을 이용하는 방법

Class<ExampleData> aClass = ExampleData.class;

2. 인스턴스에서 얻는 방법

ExampleData exampleData1 = new ExampleData();
Class<? extends ExampleData> aClass = exampleData1.getClass();

3. 문자열로 찾는 방법

String className = "org.reflection.data.ExampleData";
Class<?> aClass = Class.forName(className);

 

ClassAPI를 이용하여 다음과 같이 클래스 메타데이터에 접근, 조회 가능합니다.

Class<ExampleData> aClass = ExampleData.class;

System.out.println(aClass.getName()); // 클래스의 전체 이름(패키지 포함) 출력
System.out.println(aClass.getSuperclass()); // 부모 클래스 정보 출력
System.out.println(aClass.isInterface()); // 해당 클래스가 인터페이스인지 여부 출력

// 클래스의 접근 제어자 값을 정수로 받아서, 문자열 형태로 변환 후 출력
int m = aClass.getModifiers();
System.out.println(Modifier.toString(m));

 

2.2) java.lang.reflect.Method

클래스에 선언된 메서드 정보를 나타내며, 메서드의 이름, 매개변수, 반환형 등 정보를 제공하고, invoke() 메서드를 통해 동적 호출이 가능합니다.

 

- 메소드 정보 확인

ExampleData exampleData = new ExampleData();

//getMethods: 부모클래스 포함, public인 methods 조회
Method[] methods = exampleData.getClass().getMethods();

for (Method method: methods) {
     System.out.println("method: " + method);
     }
     
     
//getDeclaredMethods: 해당 클래스에서만 선언한 메서드들(private 및 기타 포함) 
Method[] declaredMethods = basicDataClass.getDeclaredMethods();
for (Method method: declaredMethods) {
     System.out.println("method: " + method);
     }

리플렉션을 사용하면 클래스의 모든 메서드를 반복문을 통해 조회하거나, 특정 이름을 가진 메서드를 검색할 수 있습니다.

예를 들어, getDeclaredMethods()를 사용하면 클래스에 선언된 모든 메서드의 정보를 배열로 받아올 수 있으며, 각 Method 객체에서 메서드 이름(getName()), 매개변수 정보(getParameterTypes()), 반환형(getReturnType()), 그리고 접근 제어자(getModifiers()) 등의 상세 정보를 추출할 수 있습니다.

 

- 동적 호출 및 접근 제어

ExampleData exampleData = new ExampleData();
Method helloMethod = exampleData.getClass().getDeclaredMethod("hello");

// private 메서드일 경우 접근 제한 해제
helloMethod.setAccessible(true);

// 동적 메서드 호출 (매개변수 없이 호출하는 경우)
helloMethod.invoke(exampleData);

만약 메서드가 private과 같이 접근 제한자를 가지고 있다면, setAccessible(true) 메서드를 통해 접근 제한을 무시하고 Method.invoke()를 통해 호출할 수 있습니다.

 

2.3) java.lang.reflect.Field

클래스의 필드 정보를 담으며, get()과 set() 메서드를 통해 필드 값을 읽거나 수정할 수 있습니다.

 

- 필드 정보 조회/수정

ExampleData exampleData = new ExampleData();
Field field = exampleData.getClass().getDeclaredField("someField");

// private 필드 접근이 필요한 경우 접근 제한 해제
field.setAccessible(true);

// 필드 값을 읽음
Object fieldValue = field.get(exampleData);
System.out.println("Field value: " + fieldValue);

// 필드 값을 수정함
field.set(exampleData, "새로운 값");

Field 객체를 통해 필드의 이름(getName()), 타입(getType()), 접근 제어자(getModifiers()) 등 세부 정보를 확인할 수 있습니다. 또한 set()을 통해 해당 값을 변경 할 수 있습니다. Method와 마찬가지로 setAccessible(true)를 통해 private 필드를 읽고 수정할 수 있습니다.

 

2.4) java.lang.reflect.Constructor

생성자 정보를 나타내며, 이를 통해 객체의 인스턴스를 동적으로 생성할 수 있습니다.

 

- 생성자 정보 조회

Class<ExampleData> clazz = ExampleData.class;

// 공개된 생성자 조회 및 출력
Constructor<?>[] constructors = clazz.getConstructors();
for (Constructor<?> constructor : constructors) {
    System.out.println("Public Constructor: " + constructor);
}

// 모든 생성자(비공개 포함) 조회
Constructor<?>[] declaredConstructors = clazz.getDeclaredConstructors();
for (Constructor<?> constructor : declaredConstructors) {
    System.out.println("Declared Constructor: " + constructor);
}

 

- 동적으로 객체 생성

// 매개변수를 가지는 특정 생성자 선택 (예: String 타입 매개변수를 갖는 생성자)
Constructor<ExampleData> constructor = clazz.getDeclaredConstructor(String.class);

// private 생성자일 경우 접근 제한 해제
constructor.setAccessible(true);

// 동적으로 인스턴스 생성
ExampleData instance = constructor.newInstance("생성자 매개변수 값");

 


 

3. 활용 및 주의 사항

자바 리플렉션(Reflection)은 일반적인 애플리케이션 개발에서는 잘 사용되지 않으며, 주로 프레임워크 개발이나 테스트 코드 작성 시 활용됩니다. 그 이유는 다음과 같습니다.

  1. 코드의 가독성 저하
    • 리플렉션을 사용하면 코드의 흐름을 쉽게 파악하기 어려워지고, 유지보수가 어려워질 수 있습니다.
  2. 안정성 저하
    • 컴파일 시점이 아닌 런타임 시점에 동적으로 메서드나 필드를 접근하기 때문에, 코드 실행 중에 예기치 않은 예외(예: NoSuchMethodException, IllegalAccessException)가 발생할 가능성이 높습니다.
  3. 캡슐화 원칙 위반
    • private 필드나 메서드에 setAccessible(true)를 사용하여 강제로 접근하면 객체의 내부 구현을 외부에서 변경할 수 있어, 캡슐화가 깨집니다.

이러한 이유로, 리플렉션은 스프링 프레임워크같은 프레임워크 내부 동작이나 테스트 코드에서 특정 필드 값을 조작할 때 주로 사용됩니다. 


- 결론

사용이 적절한 경우

  • 프레임워크 및 라이브러리 개발 (예: DI, ORM, 애노테이션 기반 처리)
  • private 유닛 테스트(비공개 필드 접근)

지양해야 할 경우

  • 일반적인 애플리케이션 개발에서의 비효율적인 사용
  • 성능이 중요한 코드(반복적인 리플렉션 호출)
  • 캡슐화 원칙을 깨는 private 필드/메서드 접근