리플렉션을 언제 활용할 수 있을까요?
리플렉션은 "컴파일 타임에 타입을 알 수 없거나, 런타임에 동적으로 처리해야 할 때" 활용합니다.
대표적인 활용 사례
1. 프레임워크 / 라이브러리 개발
// Spring이 @Autowired를 처리하는 방식
for (Field field : clazz.getDeclaredFields()) {
if (field.isAnnotationPresent(Autowired.class)) {
field.setAccessible(true);
field.set(instance, container.resolve(field.getType()));
}
}
// JUnit이 @Test를 처리하는 방식
for (Method method : clazz.getDeclaredMethods()) {
if (method.isAnnotationPresent(Test.class)) {
method.invoke(testInstance);
}
}
2. 직렬화 / 역직렬화
// Jackson이 객체 → JSON 변환하는 방식
public String toJson(Object obj) throws Exception {
StringBuilder json = new StringBuilder("{");
for (Field field : obj.getClass().getDeclaredFields()) {
field.setAccessible(true);
json.append("\"").append(field.getName()).append("\":");
json.append("\"").append(field.get(obj)).append("\",");
}
return json.append("}").toString();
}
// 사용
User user = new User("kim", 25);
toJson(user); // {"name":"kim","age":"25"}
3. 어노테이션 처리기 직접 구현
// 커스텀 유효성 검사 어노테이션
@Retention(RetentionPolicy.RUNTIME)
@interface NotNull {
String message() default "null 불가";
}
// 리플렉션으로 어노테이션 처리
public void validate(Object obj) throws Exception {
for (Field field : obj.getClass().getDeclaredFields()) {
if (field.isAnnotationPresent(NotNull.class)) {
field.setAccessible(true);
if (field.get(obj) == null) {
NotNull annotation = field.getAnnotation(NotNull.class);
throw new RuntimeException(
field.getName() + ": " + annotation.message()
);
}
}
}
}
// 사용
class UserRequest {
@NotNull
private String name;
@NotNull
private String email;
}
validate(new UserRequest()); // X name: null 불가
4. 테스트에서 private 메서드/필드 검증
// private 메서드를 테스트해야 할 때
class PasswordEncoder {
private String encode(String password) {
return Base64.encode(password);
}
}
@Test
void testPrivateMethod() throws Exception {
PasswordEncoder encoder = new PasswordEncoder();
Method method = PasswordEncoder.class
.getDeclaredMethod("encode", String.class);
method.setAccessible(true);
String result = (String) method.invoke(encoder, "password123");
assertEquals("cGFzc3dvcmQxMjM=", result);
}
// private 메서드 테스트가 필요하다면 설계를 먼저 고민할 것
5. 플러그인 / 동적 로딩 시스템
// 런타임에 외부 클래스를 동적으로 로드
public class PluginLoader {
public Plugin load(String className) throws Exception {
// 컴파일 타임에 어떤 클래스인지 모름
Class<?> clazz = Class.forName(className);
return (Plugin) clazz.getDeclaredConstructor().newInstance();
}
}
// 설정 파일로 구현체 교체 가능
// config.properties: plugin.class=com.example.NewPlugin
String className = config.getProperty("plugin.class");
Plugin plugin = loader.load(className); // 동적 로딩
plugin.execute();
6. ORM (JPA 내부 동작)
// JPA가 DB 결과를 엔티티 객체에 매핑하는 방식
public <T> T mapResultSet(ResultSet rs, Class<T> clazz) throws Exception {
T instance = clazz.getDeclaredConstructor().newInstance();
for (Field field : clazz.getDeclaredFields()) {
field.setAccessible(true);
// DB 컬럼명과 필드명 매핑
Object value = rs.getObject(field.getName());
field.set(instance, value);
}
return instance;
}
활용 기준 정리
| 상황 | 리플렉션 활용 | 이유 |
|---|---|---|
| 프레임워크 개발 | O | 사용자 클래스를 컴파일 타임에 알 수 없음 |
| 직렬화/역직렬화 | O | 모든 타입을 범용으로 처리해야 함 |
| 커스텀 어노테이션 처리 | O | 런타임에 어노테이션 정보 읽어야 함 |
| 플러그인 동적 로딩 | O | 런타임에 클래스 결정 |
| 일반 비즈니스 로직 | X | 설계로 해결 가능, 리플렉션 불필요 |
| private 메서드 테스트 | 가능하나 설계 문제 신호 | |
리플렉션은 "컴파일 타임에 알 수 없는 것을 런타임에 처리해야 할 때" 쓰는 도구입니다. 일반 비즈니스 로직에서 쓸 일이 생겼다면, 리플렉션보다 설계를 먼저 의심해야 합니다.