Reflection

2025. 5. 6. 02:11·DEVELOP/SPRING

Spring을 학습할수록 ‘리플렉션’이라는 용어를 자주 접하게 된다.
DI, JPA, 요청 바인딩 등 스프링의 주요 기능들이 모두 이 개념과 밀접하게 관련되어 있기 때문이다.

하지만 리플렉션이 정확히 어떤 개념이고, 어떻게 동작하며, 왜 스프링에서 핵심적인 역할을 하는지는 처음에는 쉽게 와닿지 않는다.

이 글에서는 리플렉션의 개념을 정의하고, 자바에서 제공하는 Reflection API의 구조를 살펴본 뒤,
스프링과 JPA에서 이를 어떻게 활용하고 있는지를 구체적으로 예시를 통해 설명하고자 한다.

 

리플렉션(Reflection)이란?

넓은 관점에서 리플렉션의 정의는 프로그래밍에서 런타임에 프로그램의 구조와 동작을 조사하고 수정할 수 있는 능력이다.

즉, 프로그램이 실행되는 동안 자신의 코드에 접근하여 클래스, 메서드, 필드 등의 정보를 얻거나 객체를 생성하거나 메서드를 호출하는 것 등을 할 수 있다.

좁은 관점에서 리플렉션은 자바 표준 라이브러리(java.lang.reflect 등)를 통해 객체의 클래스 정보를 런타임에 조사하고 조작할 수 있는 API이다. 이 API를 통해서 클래스의 구조, 메서드, 필드 등에 대한 상세한 정보를 얻고 실행 중 동적으로 다룰 수 있다.

 

리플렉션 예시

1. 클래스에 접근하기

Class<?> clazz = String.class;

// 클래스 이름 얻기
System.out.println("Class name: " + clazz.getName());

// 모든 메소드 얻기
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
    System.out.println("Method name: " + method.getName());
}

// 모든 필드 얻기
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
    System.out.println("Field name: " + field.getName());
}
  • Class 객체를 사용하여 클래스의 이름, 메서드, 필드, 생성자 등 다양한 정보를 얻을 수 있다. 예를 들어, 클래스의 이름을 얻거나, 해당 클래스에서 정의된 모든 메서드와 필드의 목록을 얻는 것이 가능하다.
  • 예시코드에서는 String.class를 사용해 String 클래스에 대한 Class 객체를 얻는다. 그 다음에 이 객체를 사용해서 클래스의 이름, 선언된 메서드, 필드를 얻고 출력한다. 이런 식으로 리플렉션을 활용하면 실행 중인 프로그램이 자신의 구조를 파악하고, 필요에 따라 동적으로 클래스의 속성이나 메서드를 사용할 수 있게 되는 것이다.

 

2. 필드에 접근하고 수정하기

class Example {
    public int publicField;
    private String privateField;
}

Class<?> clazz = Example.class;
Field publicField = clazz.getField("publicField");
Field privateField = clazz.getDeclaredField("privateField");

// private 필드에 접근하기 위해서는 접근 가능하도록 설정
privateField.setAccessible(true);

...

// 필드 수정하기
Example example = new Example();

// public 필드 읽기
int publicFieldValue = (Integer) publicField.get(example);
System.out.println("Public Field Value: " + publicFieldValue);

// private 필드 읽기
String privateFieldValue = (String) privateField.get(example);
System.out.println("Private Field Value: " + privateFieldValue);

// 필드 값 수정
publicField.set(example, 10); // public 필드 수정
privateField.set(example, "Hello World"); // private 필드 수정
  • 필드에 접근하기 위해서는 먼저 Class 객체를 통해 해당 필드의 Field 객체를 얻어야 한다. 필드에 접근하려면, getField()또는 getDeclaredField() 메서드를 사용한다. 이후 Field 객체의 get()으로 읽거나 set() 메소드로 수정을 할 수 있게 된다.

 

3. 메서드에 접근하기

class Example {
    public void publicMethod() {
        System.out.println("Public method");
    }

    private void privateMethod() {
        System.out.println("Private method");
    }
}

Class<?> clazz = Example.class;
Method publicMethod = clazz.getMethod("publicMethod");
Method privateMethod = clazz.getDeclaredMethod("privateMethod");

// private 메소드에 접근하기 위해서는 접근 가능하도록 설정
privateMethod.setAccessible(true);
  • 메서드에 접근하기 위해서는 먼저 Class 객체를 통해서 해당 메서드의 Method 객체를 얻어야 한다. 메서드에 접근하려면 getMethod() (public 메서드만 접근) 또는 getDeclaredMethod() 메서드 (접근 제어자에 상관없이 클래스 내 모든 메서드에 접근)를 사용한다.
  • 메서드를 실행하기 위해서는 invoke() 메서드를 사용한다. invoke() 메서드에는 메서드를 실행할 객체와 매개변수를 전달해야 한다. 디버깅할 일이 생기면 이 메서드를 확인할 수 있는데 이 메서드가 보인다면 스프링부트가 어떤 메서드를 동적으로 호출하고 있다고 이해하면 된다.
Example example = new Example();

// public 메소드 실행
publicMethod.invoke(example);

// private 메소드 실행
privateMethod.invoke(example);

 

 

4. 생성자 조작

 

리플렉션을 사용하면 클래스의 생성자에 접근하여 객체를 동적으로 생성할 수 있다.

생성자에 접근하기 위해서는 먼저 Class 객체를 통해 해당 생성자의 Constuctor 객체를 얻어야 한다. getConstructor() 또는 getDeclaredConsturctor() 메소드를 사용해 생성자에 접근할 수 있다.

  • getConstructor() - public 생성자에 접근할 때 사용
  • getDeclaredConstructor() - 접근 제어자에 상관없이 클래스에 선언된 모든 생성자에 접근할 때 사용
class Example {
    public Example() {
    }

    private Example(String arg) {
    }
}

Class<?> clazz = Example.class;
Constructor<?> publicConstructor = clazz.getConstructor();
Constructor<?> privateConstructor = clazz.getDeclaredConstructor(String.class);

// private 생성자에 접근하기 위해서는 접근 가능하도록 설정
privateConstructor.setAccessible(true);

// 객체 생성은?

// public 생성자를 사용한 객체 생성
Example example1 = (Example) publicConstructor.newInstance();

// private 생성자를 사용한 객체 생성
Example example2 = (Example) privateConstructor.newInstance("argument");

 

 

실전 예시 with JPA

JPA가 왜 기본 생성자를 요구하는지를 리플렉션의 관점으로 함께 이해할 수 있다.

Spring Data JPA는 엔티티 객체를 우리가 new로 직접 생성하지 않아도, DB에서 꺼낼 때 알아서 객체로 만들어서 반환해준다. 이건 내부적으로 JPA가 리플렉션으로 엔티티 객체를 생성하기 때문이다.

JPA가 데이터를 DB에서 가져올 때는 Post 클래스를 리플렉션으로 로딩 -> 기본 생성자 찾기 -> 객체 생성 순서로 동작한다.

 

1. Post 클래스를 리플렉션으로 로딩

Class<?> clazz = Post.class;

 

2. 기본 생성자 찾기

Constructor<?> constructor = clazz.getDeclaredConstructor();

 

3. 객체 생성

constructor.setAccessible(true); 
Object entity = constructor.newInstance();

 

리플렉션을 통한 객체 생성은 Constructor.newInstance()를 사용하는데, 이 메서드는 매개변수가 없는 생성자만 호출 가능해야 한다는 전제가 있다. 즉, DB에서 값을 꺼내오기 전에 일단 빈 객체부터 만들어놓고 그 다음에 필드 값을 하나씩 세팅(setField)하거나, 프록시로 채우는 방식이기 때문에, 매개변수가 없는 기본 생성자가 필요하다.

 

 

리플렉션 in Spring

자바의 리플렉션은 프레임워크와 라이브러리에서 매우 중요하게 사용된다. 특히 스프링 프레임워크에서는 리플렉션을 통해 많은 핵심 기능들을 구현한다.

 

1. 의존성 주입(DI)

@Service
public class PostService {
    @Autowired
    private PostRepository postRepository;
}
  • Spring은 이 PostService 클래스의 필드, 생성자 정보를 리플렉션으로 읽고 거기에 @Autowired가 붙었는지 확인하고 postRepository에 알맞은 객체(Bean)를 자동으로 넣어준다. 개발자가 new PostService()도 안했는데, 알아서 주입이 되는 건 Spring이 리플렉션으로 조작했기 때문이다.

 

2. JPA 매핑

@Entity
public class Post {
    @Id
    private Long id;
    private String title;
}
  • JPA는 클래스에 @Entity, @Id, @Column 등이 붙어 있는지 리플렉션으로 확인하고, DB 테이블과 자동으로 연결된다. postRepository.save(post)했을 때, 객체 안의 값을 필드마다 꺼내서 SQL로 변환한다.

 

3. 요청 바인딩

@PostMapping("/posts")
public void createPost(@RequestBody PostRequestDto dto) { ... }
  • Spring은 HTTP 요청 바디에서 JSON을 읽고, PostReqeustDto 클래스의 필드를 리플렉션으로 파악해서 값을 자동으로 넣어준다.

 

리플렉션 관점에서 Spring 프레임워크의 정의

Spring은 리플렉션을 통해 개발자의 코드 구조와 어노테이션을 분석하고, 런타임에 객체 생성, 의존성 주입, 요청 바인딩 등 다양한 작업을 자동으로 처리해주는 프레임워크이다.

 

 

REFERENCE

https://curiousjinan.tistory.com/entry/java-reflection-explain

 

[Java] 자바 리플렉션(reflection)이란?

이번 포스트에서는 자바 리플렉션과 이걸 사용하는 스프링에 대해서 알아보자 1. 자바 리플렉션이란 리플렉션이란? 리플렉션은 자바에서 클래스나 멤버에 대한 정보를 런타임에 조사하고, 조작

curiousjinan.tistory.com

 

'DEVELOP > SPRING' 카테고리의 다른 글

[SPRING/JPA] Fetch 전략  (1) 2025.05.02
생성자 주입/필드 주입/setter 주입  (0) 2025.04.25
IoC와 DI  (0) 2025.04.25
'DEVELOP/SPRING' 카테고리의 다른 글
  • [SPRING/JPA] Fetch 전략
  • 생성자 주입/필드 주입/setter 주입
  • IoC와 DI
콘순이
콘순이
개발 보안 관련 스터디 기록장
  • 콘순이
    SECURITY DEVELOPER
    콘순이
  • 글쓰기 관리
  • 전체
    오늘
    어제
    • 분류 전체보기 (69)
      • BAEKJOON (45)
      • ALGORITHM (4)
      • QUALIFICATIONS (0)
      • PYTHON (1)
      • PROGRAMMERS (6)
      • DEVELOP (10)
        • SPRING (4)
        • ERROR (0)
        • CONCEPT (5)
        • AWS (1)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    비트 조작
    알고리즘
    solid
    문자열
    Python
    비트 마스킹
  • 최근 댓글

  • hELLO· Designed By정상우.v4.10.3
콘순이
Reflection
상단으로

티스토리툴바