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)은 일반적인 애플리케이션 개발에서는 잘 사용되지 않으며, 주로 프레임워크 개발이나 테스트 코드 작성 시 활용됩니다. 그 이유는 다음과 같습니다.
- 코드의 가독성 저하
- 리플렉션을 사용하면 코드의 흐름을 쉽게 파악하기 어려워지고, 유지보수가 어려워질 수 있습니다.
- 안정성 저하
- 컴파일 시점이 아닌 런타임 시점에 동적으로 메서드나 필드를 접근하기 때문에, 코드 실행 중에 예기치 않은 예외(예: NoSuchMethodException, IllegalAccessException)가 발생할 가능성이 높습니다.
- 캡슐화 원칙 위반
- private 필드나 메서드에 setAccessible(true)를 사용하여 강제로 접근하면 객체의 내부 구현을 외부에서 변경할 수 있어, 캡슐화가 깨집니다.
이러한 이유로, 리플렉션은 스프링 프레임워크같은 프레임워크 내부 동작이나 테스트 코드에서 특정 필드 값을 조작할 때 주로 사용됩니다.
- 결론
사용이 적절한 경우
- 프레임워크 및 라이브러리 개발 (예: DI, ORM, 애노테이션 기반 처리)
- private 유닛 테스트(비공개 필드 접근)
지양해야 할 경우
- 일반적인 애플리케이션 개발에서의 비효율적인 사용
- 성능이 중요한 코드(반복적인 리플렉션 호출)
- 캡슐화 원칙을 깨는 private 필드/메서드 접근
'컴퓨터 > JAVA' 카테고리의 다른 글
Spring - Log 레벨 이해 및 JAVA의 로그 프레임워크 종류 (0) | 2025.01.16 |
---|---|
JPA - Spring Boot의 @RestController, Hibernate Lazy Loading 직렬화 (0) | 2024.12.19 |
JPA - 연관관계 매핑 2(프록시 객체) (0) | 2024.12.05 |
JPA - 연관관계 매핑 1(외래키 설정) (0) | 2024.11.28 |
JPA - 영속성 컨텍스트(Persistence Context) (0) | 2024.11.23 |