개발을 하다보면 익숙하게 사용하는 기술의 동작원리가 궁금해질 때가 있습니다.
Spring은 어떻게 실행시점에 빈을 주입할 수 있는 걸까?
JPA의 Entity는 왜 꼭 기본 생성자를 가져야만 할까?
두 질문을 관통하면 키워드에는 Reflection이라는 것이 있습니다.
Java를 사용하면서 Reflection이라는 단어를 들어본 경험이 한 번 쯤음 있을 것입니다.
그렇지만 평소 잘 사용하지 않는 단어이고 익숙하지 않은 사람들이 대부분일 것이라고 생각합니다.
오늘은 Reflection이 무엇인지 알아보고 도대체 왜 쓸까 그리고 어디에 활용이 가능할지 알아보겠습니다.
사전적 의미는 '거울에 반사된 상' 🪞↪️
동적으로 클래스 정보에 접근해서 객체 생성, 메서드 실행 등 처리를 가능하게 해주는 API로java.lang.reflect
패키지를 제공합니다.
[JVM 메모리 영역]
.java
소스코드 -> .class
바이트코드메서드 영역
에 인스턴스 정보는 힙 영역
에 저장됩니다.[리플렉션 핵심]
아래에서는 리플렉션 핵심 3가지를 살펴보겠습니다.
클래스 정보 접근이란 JVM 코드표 영역에 접근하는 것 입니다.
접근하는 방법으로는 3가지 방법이 존재합니다.
[접근 방법]
1. Class<?> {클래스이름}.class
2. Class<?> Class.forName(String {클래스이름})
3. Class<?> {객체이름}.getClass()
이렇게 3가지 방법을 통해서 상황에 맞게 사용이 가능하겠네요~.~
[예제]
// 가져온 클래스 정보를 통해, 클래스 상세 정보들 조회하기
Class<?> clz = {클래스이름}.class; // 1번 접근 방법 사용
System.out.println(clz.getName()); // com.XXX.XXX.클래스이름
System.out.println(clz.getSimpleName()); // 클래스이름
[접근 방법]
1. Method[] Class.getDeclaredMethods()
2. Method getDeclaredMethod(String name, Class<?> … param Types)
[예제]
// 가져온 클래스의 메서드를 가져오기
Class<?> clz = 클래스이름.class;
Method[] mArr = clz.getDeclaredMethods();
// 1. 클래스가 가지고 있는 모든 메서드 조회
for (Method m : mArr) {
System.out.println("메서드 이름: " + m.getName());
}
// 2. 클래스가 가지고 있는 1개의 메서드 조회
Method m = clz.getDeclaredMethod("메서드 이름", 자료형.class);
System.out.println(m.getName());
생성자 얻기 : Constructor<?> Class.getDeclaredConstructor(Class<?> … params)
객체 생성 : Object Constructor newInstance(Object … args)
[예제]
// 클래스 클래스이름 = new 클래스(); 사실 이게 하고 싶음!!...
// 위와 동일한 결과로 생성이 가능하다 !!
Class<?> clz = 클래스.class;
Constructor<?> constructor = clz.getDeclaredConstructor(); // 파라미터가 없기 때문에 기본 생성자를 가져온다.
클래스 클래스이름 = constructor.newInstance();
// 클래스 클래스이름 = 클래스.class.getDeclaredConstructor().newInstance(); // 인라인 코드
// 근데 이걸 왜 함?? 클래스에 관한 정보를 지정해 줄 수 없는 동작 시점에 사용이 가능하다 ㄷㄷ
// 동적으로 동작하게 만들기 위한 원리이다.
// 리플렉션을 이용하여 객체 생성
Object Method invoke(Object target, Object … args)
@Autowired
와 같은 DL, DI 기능 (: ProcessInject(), Inject() Method)Java는 기본적으로 컴파일러를 사용합니다. (물론 Java는 컴파일러와 인터프리터를 모두 사용하는 JIT 컴파일러)
즉, 컴파일 시점에 타입을 결정합니다.
Reflection API를 통해서, 클래스의 이름만 가지고도 이미 로딩이 완료된 클래스에서 또 다른 클래스를 동적으로 로딩하여 생성자, 멤버 필드 그리고 멤버 메서드 등을 사용할 수 있도록 합니다.
주로 프레임워크와 라이브러리에서 사용하는데, 컴파일 시점엔 객체의 타입을 모르기 때문에 동적으로 해결하기 위해서 사용합니다.
Spring 교육을 들으면서, 클래스 객체를 만들 때 “기본 생성자를 꼭 만들어줘야한다. 안그러면 쫑난다.” 라는 말을 담당 교수님에게 들었던 적이 있습니다.
왜 만들어줘야할까?.. 그 당시에는 잘 몰랐지만, Reflection을 공부하면서 알게된 내용은 이러합니다.
Reflection이 가져올 수 없는 정보 중 하나는 바로 생성자의 인자 정보들 입니다.
따라서 기본 생성자 없이 파라미터가 있는 생성자가 존재한다면 Reflection이 객체를 생성할 수 없게 됩니다.
(기본 생성자를 사용하는 이유는?
기본 생성자로 객체를 생성하고, 필드를 통해 값을 넣어주는 것이 가장 간단한 방법이기 때문입니다.)
Reflection은 JVM 영역내에 존재하는 클래스의 정보를 가져와 사용할 수 있기 때문에 무궁무진한 활용이 가능합니다.
그렇지만, 거치게 되는 단계가 그만큼 많아 지는 것이므로 여러가지 단점을 수반합니다.
따라서 꼭 필요한 경우에만 사용하는 것을 권장합니다 😎
출처:
https://www.flaticon.com/kr/free-icon/infinity_5117822
https://www.youtube.com/watch?v=67YdHbPZJn4