자바의 리플렉션(Reflection)은 구체적인 클래스 타입을 알지 못하더라도 런타임 시점에 클래스의 메타데이터(클래스 구조, 메서드, 필드, 생성자 등)에 접근하고 이를 조작할 수 있게 해주는 기능이다. 작성 시점에는 알 수 없는 클래스를 동적으로 로딩하여 사용할 수 있게 해주며, Spring Framework, ORM(JPA/Hibernate), 테스트 프레임워크(JUnit) 등 자바 생태계의 다양한 프레임워크와 라이브러리에서 핵심적인 기반 기술로 활용된다.
리플렉션을 사용하기 위해서는 가장 먼저 해당 클래스의 정보를 담고 있는 Class 객체를 얻어야 한다. 대표적으로 세 가지 방법이 존재한다.
클래스명.class: 클래스 리터럴을 직접 사용하는 방식객체.getClass(): 이미 생성된 객체 인스턴스를 통해 호출하는 방식Class.forName("패키지명을 포함한 클래스명"): 문자열 형태의 클래스 이름으로 런타임에 로딩하는 방식 (클래스가 존재하지 않을 경우 ClassNotFoundException 발생)// 1. 클래스 리터럴
Class<User> clazz1 = User.class;
// 2. getClass()
User user = new User();
Class<? extends User> clazz2 = user.getClass();
// 3. Class.forName()
Class<?> clazz3 = Class.forName("com.example.User");
획득한 Class 객체를 활용하면 클래스 내부에 정의된 요소들을 탐색하고 동적으로 제어할 수 있다.
// 파라미터가 없는 기본 생성자 획득
Constructor<?> constructor = clazz1.getDeclaredConstructor();
User userInstance = (User) constructor.newInstance();
getDeclaredField()를 사용하면 private으로 선언된 필드에도 접근할 수 있다. 접근 제어자를 우회하기 위해서는 setAccessible(true)를 호출해야 한다.
Field nameField = clazz1.getDeclaredField("name");
nameField.setAccessible(true); // private 접근 허용
// 필드 값 변경
nameField.set(userInstance, "홍길동");
// 매개변수가 없는 메서드 획득
Method printMethod = clazz1.getDeclaredMethod("printInfo");
printMethod.setAccessible(true);
// 메서드 실행 (invoke)
printMethod.invoke(userInstance);
일반적인 비즈니스 로직 개발에서 리플렉션을 직접 사용하는 경우는 드물다. 하지만 프레임워크 단에서는 유연한 구조를 위해 활발히 쓰인다.
@Autowired를 통한 의존성 주입(DI), @Controller 등 애노테이션 기반 컴포넌트 스캔 과정에서 동적으로 객체를 생성하고 결합할 때 사용한다.@Test 애노테이션이 붙은 메서드를 동적으로 탐색하여 실행한다.쉽고 확실하게 직관적으로 이해할 수 있는 현실 세계 비유 예시와 간단한 코드 예시로 설명해 준다.
우리가 웹 서버를 만들 때 @AutoWired나 @Test 같은 애노테이션을 붙여두면 알아서 실행되는 원리가 바로 이것이다.
public class Robot {
private String secretKey = "1234-5678"; // private 변수
public void powerOn() {
System.out.println("로봇 전원 ON!");
}
}
프레임워크를 만드는 사람은 내가 Robot이라는 클래스를 만들지, Car라는 클래스를 만들지 코드 작성 시점에는 전혀 모른다.
public class FrameworkEngine {
public void run(Object obj) throws Exception {
// 1. 들어온 객체의 클래스 정보를 런타임에 엿본다 (X-ray 스캔)
Class<?> clazz = obj.getClass();
System.out.println("클래스 이름: " + clazz.getName()); // "Robot" 출력
// 2. private 필드에 접근해서 값을 강제로 꺼내온다
Field field = clazz.getDeclaredField("secretKey");
field.setAccessible(true); // private 접근 허용!
String key = (String) field.get(obj);
System.out.println("비밀키 추출: " + key); // "1234-5678" 출력
// 3. powerOn 메서드를 찾아서 실행시킨다
Method method = clazz.getDeclaredMethod("powerOn");
method.invoke(obj); // "로봇 전원 ON!" 출력
}
}
리플렉션은 강력한 유연성을 제공하지만 분명한 단점이 존재하므로 신중하게 사용해야 한다.
NoSuchMethodException, IllegalAccessException 등)가 발생한다.setAccessible(true)를 통해 private 멤버에 직접 접근이 가능해지므로, 객체 지향의 캡슐화 원칙을 저해할 수 있다.자바 리플렉션은 런타임에 클래스의 정보에 접근하여 동적으로 객체를 생성하고 조작할 수 있는 강력한 기능이다. 프레임워크와 라이브러리 개발에 있어서 필수적인 유연성을 제공하지만, 성능 저하, 타입 안전성 결여, 캡슐화 파괴라는 위험 요소도 함께 동반된다. 따라서 일반적인 애플리케이션 개발 시에는 표준적인 정적 타입 명시 방식을 우선으로 하고, 리플렉션은 필요한 상황에 한해 제약적으로 활용하는 것이 바람직하다.
private까지 강제로 열 수 있어 안전성/캡슐화가 깨질 수 있다.