리플렉션을 언제 활용할 수 있을까요?

리플렉션은 "컴파일 타임에 타입을 알 수 없거나, 런타임에 동적으로 처리해야 할 때" 활용합니다.

대표적인 활용 사례

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 메서드 테스트가능하나 설계 문제 신호

리플렉션은 "컴파일 타임에 알 수 없는 것을 런타임에 처리해야 할 때" 쓰는 도구입니다. 일반 비즈니스 로직에서 쓸 일이 생겼다면, 리플렉션보다 설계를 먼저 의심해야 합니다.

CSLOG에서 보기