자바 언어·플랫폼 용어 사전
JVMreflection · Class.forName · setAccessible

리플렉션

실행 중에 이름만으로 클래스를 찾아 쓰는 기능. 프레임워크가 우리 코드를 다루는 원리다.

실행 중에 이름만으로 클래스를 찾아 객체를 만들고 메서드를 부르는 기능.

Class<?> c = Class.forName("com.shop.OrderService");
Object o = c.getDeclaredConstructor().newInstance();
Method m = c.getDeclaredMethod("save", Order.class);
m.setAccessible(true);          // private 도 뚫는다
m.invoke(o, order);

컴파일 시점에 그 클래스를 몰라도 된다. 클래스 정보가 메타스페이스에 그대로 남아 있기 때문에 가능한 일이다.

프레임워크가 우리 코드를 다루는 원리

  • JPA 가 엔티티의 private 필드에 값을 채운다
  • Jackson 이 JSON 키를 보고 세터·필드를 찾는다
  • JUnit 이 @Test 붙은 메서드를 찾아 실행한다
  • 스프링이 @Autowired 필드에 빈을 꽂는다

전부 "이름으로 찾아 쓴다" 는 같은 동작이다. 우리가 프레임워크를 부르는 게 아니라 프레임워크가 우리를 부르는 구조가 이것으로 만들어진다.

대가

  • 느리다 — 접근 검사와 인자 박싱이 붙는다. 다만 JIT 가 인라인 캐시를 만들면 반복 호출은 상당히 회복되므로, "리플렉션은 무조건 느리다" 는 과장이다
  • 컴파일러 검사를 잃는다 — 메서드 이름 오타가 컴파일이 아니라 런타임에 NoSuchMethodException 으로 터진다. 리팩터링 도구도 문자열은 못 따라간다
  • 캡슐화를 뚫는다setAccessible(true)private 에 접근한다

JPMS 가 이 통로를 통제한다

module com.shop.order {
    exports com.shop.order.api;      // 일반 접근 허용
    opens   com.shop.order.entity;   // 리플렉션만 허용
}
  • exports 는 컴파일·실행 모두에서 쓸 수 있게 연다
  • opens리플렉션 전용이다. JPA·Jackson 이 필요로 하는 것이 이쪽이다

Java 17 부터 JDK 내부에 대한 강한 캡슐화가 기본이라, 낡은 라이브러리가 여기서 InaccessibleObjectException 으로 깨진다. --add-opens 로 임시로 여는 것이 마이그레이션에서 가장 자주 만나는 조치다.

줄어드는 흐름도 있다

네이티브 이미지(GraalVM)나 AOT 는 실행 전에 무엇이 쓰일지 알아야 하므로 런타임 탐색과 상성이 나쁘다. 그래서 최근 프레임워크는 리플렉션 대신 빌드 시점 코드 생성으로 옮겨 가고 있다. 같은 일을 하되 결정 시점을 앞당기는 것이다.

면접 함정

  • "리플렉션은 항상 느려서 쓰면 안 된다" → 반복 호출은 JIT 가 상당히 회복시킨다. 문제는 속도보다 타입 안전성 상실이다.
  • "public 이면 리플렉션으로 다 접근된다" → 모듈이 opens 하지 않으면 막힌다.

함께 보면 좋은 용어

노트에서 맥락과 함께 보기 — JVM 실행 흐름 — 내 코드가 도는 과정