구체적인 클래스의 타입을 알지 못하더라도 그 클래스의 메서드, 타입, 변수 등에 접근할 수 있도록 하는 자바 API
실행 시점에 클래스의 구조와 정보를 분석하고, 객체를 동적으로 생성 및 조작할 수 있도록 하는 자바 기능
왜 사용할까 ?
컴파일 시점에 타입과 구조를 알 수 없는 객체를 실행 시점에 다루기 위함입니다.
프레임워크의 컴파일 시점에는 사용자 객체의 타입과 구조를 알 수 없다
리플렉션은 JPA와 같은 프레임워크가 컴파일 시점에 알 수 없는 사용자 클래스의 타입과 구조를, 런타임에 동적으로 분석하고 처리하기 위해 사용됩니다.
어노테이션 기반 동작
클래스에 있는 어노테이션을 해석하여 해당 클래스의 동작을 결정
ex ) 런타임 때, @Transactional이 존재하면 트랜잭션 관련 코드 실행
객체를 동적으로 생성
컴파일 시점에 어떤 클래스의 객체를 생성할지 결정하지 않고, 실행 시점에 클래스 메타정보를 읽어 리플렉션을 통해 객체를 생성하는 방식
ex ) JPA 엔티티 생성, Spring Bean 생성, JSON → 객체 역직렬화 ( Jackson )
cf ) findById, JPQL 조회 등에서 JPA가 엔티티 객체를 생성할 때, 기본 생성자를 리플렉션으로 호출하기 때문에 기본 생성자가 필수입니다.
( 리플렉션으로 기본 생성자를 호출하여 엔티티 객체를 먼저 생성한 뒤, 필드에 값을 주입하여 엔티티를 완성 )
프록시와 AOP 구현
프록시는 대상 객체에 어떤 메서드가 존재하는지 컴파일 시점에 알 수 없기 때문에, 런타임에 리플렉션을 사용해 메서드를 동적으로 호출합니다.
또한, AOP 적용 대상 메서드를 미리 알 수 없으므로 동일한 방식으로 동작합니다.
@Transactional이 적용된 메서드는 애플리케이션 실행 시점에 프록시 객체로 감싸져 빈으로 등록됩니다.
프록시는 대상 객체의 메서드 정보를 알고 있지만, 어떤 메서드가 실제로 호출될지는 런타임에 결정되므로, 호출 시점에
리플렉션을 사용해 실제 메서드를 실행합니다.
즉, 실제로 실행될 메서드는 런타임에 결정되어 리플렉션을 통해 호출됩니다.
invoke(method, args) {
before(); // 공통 로직 시작
method.invoke(...); // 런타임에 결정된 메서드
after(); // 공통 로직 끝
}
실제 활용
Spring Framework
스프링에서는 리플렉션을 활용하여 의존성 주입 ( DI )를 구현합니다.
ex ) @Autowired 어노테이션 사용 시, 스프링 컨테이너가 리플렉션을 사용하여 필요한 객체를 자동으로 주입해줍니다.
Reflection의 단점
런타임에 클래스 정보를 분석하기 때문에 일반적인 메서드 호출보다 느립니다.
리플렉션을 사용하면 private 메서드나 필드에 접근할 수 있으므로, 보안 측면에서 취약점이 될 수 있습니다.
런타임에 객체 정보를 읽어서 출력하는 Reflection 사용 예제
Replection을 사용하면 private 멤버에 접근할 수 있습니다. → 보안상 취약점이 될 수 있습니다.
( 캡슐화를 위반하는 것이므로 지양해야합니다. )
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class ReflectionEntity {
private final String name;
private final int age;
public ReflectionEntity(String name, int age) {
this.name = name;
this.age = age;
}
private void privateMethod() {
System.out.println("name : " + name + " , " + "age : " + age);
}
public void useReflection() throws Exception {
ReflectionEntity myObject = new ReflectionEntity("park", 100);
Class<?> clazz = myObject.getClass();
Field secretField = clazz.getDeclaredField("name");
secretField.setAccessible(true);
System.out.println("name : " + secretField.get(myObject));
// 비공개 메서드 호출
Method secretMethod = clazz.getDeclaredMethod("privateMethod");
secretMethod.setAccessible(true);
System.out.println("privateMethod 메서드 실행");
secretMethod.invoke(myObject);
}
}
@SpringBootApplication
public class ReflectionApplication {
public static void main(String[] args) {
try {
ReflectionEntity ex = new ReflectionEntity("park", 100);
ex.useReflection();
} catch (Exception e) {
e.printStackTrace();
}
}
}
name : park
privateMethod 메서드 실행
name : park , age : 100