Reflection API
- 구체적인 클래스 타입을 알지 못해도 메서드, 타입, 변수 등 해당 클래스의 정보에 접근할 수 있게 해주는 자바 API
- 스프링 프레임워크, 마이바티스, 하이버네이트 등의 라이브러리에서 사용
- JVM에서 실행되는 애플리케이션의 런타임 동작을 검사하거나 수정할 수 있는 기능이 필요한 경우 사용
- 리플렉션을 사용해 개발자가 등록한 빈을 애플리케이션 내에서 사용할 수 있음
대표 예시
- 스프링 프레임 워크 : DI에서 사용
- MVC : View에서 넘어오는 데이터를 객체에 바인딩 할 때 사용
- Hibernate : @Entity 클래스에 setter가 없으면 해당 필드에 값을 바로 주입
- JUnit : ReflectionUtils 클래스를 내부적으로 정의하여 사용
주의사항
- 오버헤드 발생 : 성능 저하를 발생시킬 수 있어 성능에 민감한 애플리케이션에서는 사용하지 않음
- 캡슐화 저해 : 접근 지시자를 무시할 수 있어 private로 설정한 필드에도 접근 가능하기 때문에 코드 기능이 저하됨
사용 예시 코드
아래와 같은 클래스가 있다고 가정한다.
public class Person {
private String name;
private int age;
public Person() {}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public void sayHello() {
System.out.println("안녕하세요, 저는 " + name + "이고 " + age + "살입니다.");
}
}
리플렉션을 사용하여, 다양한 정보에 접근할 수 있다.
클래스에 접근
package com.example.demo.example;
import java.util.Arrays;
public class ReflectionExample1 {
public static void main(String[] args) throws Exception {
// 클래스 인스턴스에 접근하는 방법 3가지
// 1) 타입을 통해
// 클래스 로딩이 끝나면, 클래스 타입의 인스턴스를 만들어서 heap에 저장하므로 인스턴스를 가져올 수 있음
Class<?> personClass = Person.class;
// 2) 인스턴스를 통해
// 인스턴스가 이미 만들어져 있는 경우, getClass()를 사용하여 가져올 수 있음
Person person = new Person();
Class<? extends Person> examClass1 = person.getClass();
// 3) 경로를 통해
// 클래스가 없으면 ClassNotFoundException 발생
Class<?> examClass2 = Class.forName("com.example.demo.example.Person");
}
}
필드에 접근
package com.example.demo.example;
import java.util.Arrays;
public class ReflectionExample2 {
public static void main(String[] args) throws Exception {
// 모든 필드 목록 가져오기
Arrays.stream(personClass.getDeclaredFields()).forEach(System.out::println);
// public 필드 목록 가져오기
Arrays.stream(personClass.getFields()).forEach(System.out::println);
// 필드가 가지고 있는 값 목록 가져오기(객체가 있어야 함)
Arrays.stream(personClass.getDeclaredFields()).forEach(f -> {
try {
f.setAccessible(true); // 접근지시자 무시
System.out.printf("%s %s \n", f, f.get(person));
} catch (IllegalAccessException e) {
e.printStackTrace();
}
});
}
}
메서드에 접근
package com.example.demo.example;
import java.util.Arrays;
public class ReflectionExample1 {
public static void main(String[] args) throws Exception {
// 메서드 목록 가져오기
// 직접 정의한 public 메서드 이외에도, Object로 상속받은 메서드도 출력
// 만일 private 메서드도 필요하다면 getDeclaredMethods()
Arrays.stream(personClass.getMethods()).forEach(System.out::println);
}
}
생성자에 접근
package com.example.demo.example;
import java.util.Arrays;
public class ReflectionExample1 {
public static void main(String[] args) throws Exception {
Arrays.stream(personClass.getDeclaredConstructors()).forEach(System.out::println);
}
}
이외에도 getSuperClass()와 getInterfaces() 메서드로 클래스의 부모 클래스, 인터페이스와 같은 정보들에도 접근이 가능하다.
위에서의 예제 뿐만 아니라, 리플렉션을 통해 생성자로 인스턴스를 만들 수 있고, 필드의 값을 가져오거나 수정할 수 있으며, 메서드를 실행할 수도 있다.
스프링이 리플렉션을 통해 하는 일은 무엇인가?
유연성과 동적 바인딩
: 리플렉션을 사용하면 스프링은 런타임에 객체의 필드, 메서드, 생성자 등에 접근하여 그에 따라 의존성을 주입할 수 있다. 이렇게 되면 컴파일 시간에 모든 것을 결정하지 않아도 되고, 유연한 애플리케이션 구조를 만들 수 있다.
구성의 외부화
: XML이나 어노테이션을 통해 빈(Bean) 설정을 외부에서 정의할 수 있게 한다. 이렇게 하면 코드 변경 없이 애플리케이션의 행동을 바꿀 수 있다. 리플렉션 덕분에 스프링은 이런 설정 정보를 읽고, 해당 객체에 적용할 수 있다.
코드의 간결성
: 리플렉션을 사용하면 스프링 프레임워크가 많은 부분을 자동으로 처리해 주어 결과적으로 코드가 더 깔끔하고 관리하기 쉬워진다.
위에서 리플렉션에 대해 알아봤다. 그래서 오늘의 질문!
생성자 주입이 빈을 생성할 때 추가적인 리플랙션을 진행하는가?
일반적으로 생성자 주입이 일어날 때 리플렉션은 빈 생성 과정에서 한 번 사용된다. 스프링 컨테이너는 처음에 빈의 메타데이터를 읽어서 어떤 생성자를 사용해야 하는지 결정하고, 해당 생성자에 정의된 파라미터에 맞는 의존성을 주입하기 위해 리플렉션을 사용한다.
그러나 AOP와 같은 기능을 사용하거나, 프록시 객체를 생성할 때에는 추가적인 리플렉션을 진행할 수 있다.
https://engineerinsight.tistory.com/233
https://velog.io/@suyeon-jin/리플렉션-스프링의-DI는-어떻게-동작하는걸까
https://curiousjinan.tistory.com/entry/how-spring-uses-java-reflection-for-constructor-injection
'Backend > Spring' 카테고리의 다른 글
[Spring] Spring MVC란? Spring MVC에서 HTTP요청이 들어왔을 때의 흐름은? (0) | 2024.01.04 |
---|---|
[Spring] Spring AOP란? Spring AOP는 CTW, PCW, LTW, RTW 중에 무엇일까? (1) | 2024.01.03 |
[Spring] 의존성 주입(Dependency Injection) 4가지 방법 (0) | 2023.12.29 |
Configuration은 어떻게 Bean을 등록하고 관리할까? (0) | 2023.12.28 |
[Spring] 스프링 입문 - 코드로 배우는 스프링 부트 #4 스프링 빈과 의존관계 (0) | 2023.11.21 |