리플렉션이란 자바에서 클래스가 제공하는 다양한 정보를 동적으로 분석하고 사용하는 기능을 의미한다. 해당 기능을 통해 런타임 시점에 클래스에 메타데이터 정보를 얻거나, 동적으로 새로운 객체를 생성, 메서드 호출, 필드 값을 을 읽고 쓸 수 있다.
클래스 메타데이터 조회 3가지
- 클래스 타입으로 조회
Class<Target> targetClass = Target.class;
- 인스턴스에서 조회
Target target = new Target();
Class<? extends Target> targetClass = target.getClass();
- 문자(패키지+클래스명)를 통한 조회
String targetPackage = "reflection.ReflectionTest";
Class<?> targetClass = Class.forName(targetPackage);
3번의 경우를 보면 문자열을 통해 클래스의 메타데이터를 조회할 수 있다.
→ 문자열을 동적으로 입력받아 메타데이터 조회가 가능!
Class 클래스의 주요 메서드
- Class 클래스는 객체의 필드, 수정자, 생성자, 메서드 등을 getXxx 메서드를 통해 얻을 수 있다.
- 대표적으로 get 메서드에는 두 가지가 존재한다
- getXxx (getFields(), getAnnotations(), getMethods(), getModifiers() …)
- 해당 클래스와 상위 클래스에서 상속된 모든 public Xxx를 반환
- getDeclaredXxx (getDeclaredMethods(), getDeclaredConstructors()…)
- 해당 클래스에 선언된 모든 Xxx를 접근 제어자에 관계없이 반환, 상속된 메서드는 포함 X
- getXxx (getFields(), getAnnotations(), getMethods(), getModifiers() …)
아래는 해당 클래스에 선언된 메서드들을 이름으로 찾은 뒤 호출하는 코드이다.
Target 클래스에는 public void call(String message), private void call2(String message) 두 메서드가 있다.
첫 번째 call의 경우에는 문제없이 호출된다.
두 번째 call2()의 경우에는 private 접근 제어자가 선언되어 있어 호출할 수 없다고 나온다.
마지막 call3()는 없는 메서드이므로 예외가 발생한다.
일반적으로 메서드 호출을 정적으로 이루어졌지만 리플렉션을 사용하면 동적으로 메서드 이름을 받은 뒤 호출하는 것이 가능해진다.
public class ReflectionTest {
public static void main(String[] args) throws ClassNotFoundException {
Target target = new Target();
String[] methodNames = {"call", "call2", "call3"};
call(target, methodNames);
}
public static void call(Target target, String[] methodNames) {
Class<? extends Target> targetClassInfo = target.getClass();
for (String methodName : methodNames) {
try {
Method method = targetClassInfo.getDeclaredMethod(methodName, String.class);
method.invoke(target, "hello");
} catch (Exception e) {
System.out.println(methodName + " 메서드를 호출할 수 없습니다: " + e.getMessage());
}
}
}
}
Target.call : hello
call2 메서드를 호출할 수 없습니다:
class reflection.ReflectionTest cannot access a member of class reflection.Target with modifiers "private"
call3 메서드를 호출할 수 없습니다: reflection.Target.call3(java.lang.String)
리플렉션을 사용하면 또 하나 특별한 기능을 제공한다.
바로 call2()와 같이 private 접근 제어자가 붙은 경우에도 호출이 가능해진다.
기본적으로는 위와 같이 호출할 수 없도록 되어있지만 접근하려는 필드, 메서드에 setAccessible(true)
설정을 하면 접근이 가능하고 값 변경도 가능하다.
public class ReflectionTest {
public static void main(String[] args) throws ClassNotFoundException {
Target target = new Target();
String[] methodNames = {"call", "call2", "call3"};
call(target, methodNames);
}
public static void call(Target target, String[] methodNames) {
Class<? extends Target> targetClassInfo = target.getClass();
for (String methodName : methodNames) {
try {
Method method = targetClassInfo.getDeclaredMethod(methodName, String.class);
method.setAccessible(true);
method.invoke(target, "hello");
} catch (Exception e) {
System.out.println(methodName + " 메서드를 호출할 수 없습니다: " + e.getMessage());
}
}
}
}
Target.call : hello
Target.call2 : hello
call3 메서드를 호출할 수 없습니다: reflection.Target.call3(java.lang.String)
장단점
장점
- 런타임 시점에 객체 조작이 가능하므로 확장에 유리하고, 유연하다는 장점이 있다.
- 스프링은 리플렉션을 통해 런타임 시점에 메타데이터를 분석해서, 필요한 객체 생성, 주입을 한다.
- 테스트 코드 작성 시 대상 클래스의 private 메서드, 필드에 접근하여 정밀한 테스트가 가능하다.
- 주로 테스트, 프레임워크나 라이브러리 개발에 유용되게 사용된다
단점
- 위에서 본 것처럼 리플렉션을 활용하면 private 접근 제어자에 접근, 값 변경이 가능하다.
- 하지만 private 접근 제어자는 클래스 내부 데이터를 보호하고, 외부에서의 직접적인 접근을 방지하기 위해 사용되는데 이를 위반한다. → 캡슐화, 정보은닉 X
- 그리고 클래스의 구조, 구현 사항이 변경되는 경우 리플렉션을 사용한 코드는 예상치 못한 버그가 초래된다.
- 런타임에 클래스를 분석하므로 정적 메서드 호출 방법보다 느리다.
- 일반적인 애플리케이션 코드에서는 권장 X
'JAVA' 카테고리의 다른 글
[자바] URL 인코딩 (1) | 2024.12.25 |
---|---|
[자바] 애노테이션(Annotation) (1) | 2024.12.25 |
[자바] 소켓 프로그래밍 (0) | 2024.12.25 |
[자바] I/O 스트림 (0) | 2024.12.25 |
[자바] 스레드 풀, Executor (0) | 2024.12.25 |