참고
https://inpa.tistory.com/entry/JAVA-%E2%98%95-%EB%88%84%EA%B5%AC%EB%82%98-%EC%89%BD%EA%B2%8C-%EB%B0%B0%EC%9A%B0%EB%8A%94-Reflection-API-%EC%82%AC%EC%9A%A9%EB%B2%95#reflection_api_%EA%B8%B0%EB%B2%95
https://inpa.tistory.com/entry/JAVA-%E2%98%95-%EB%88%84%EA%B5%AC%EB%82%98-%EC%89%BD%EA%B2%8C-%EB%B0%B0%EC%9A%B0%EB%8A%94-Dynamic-Proxy-%EB%8B%A4%EB%A3%A8%EA%B8%B0
https://tlatmsrud.tistory.com/112
https://tecoble.techcourse.co.kr/post/2020-07-16-reflection-api/
https://inpa.tistory.com/entry/GOF-%F0%9F%92%A0-%ED%94%84%EB%A1%9D%EC%8B%9CProxy-%ED%8C%A8%ED%84%B4-%EC%A0%9C%EB%8C%80%EB%A1%9C-%EB%B0%B0%EC%9B%8C%EB%B3%B4%EC%9E%90#%EC%98%88%EC%A0%9C%EB%A5%BC_%ED%86%B5%ED%95%B4_%EC%95%8C%EC%95%84%EB%B3%B4%EB%8A%94_proxy_%ED%8C%A8%ED%84%B4
Java Reflection API란?
자바 언어는 본래 정적 언어라서 항상 타입을 알아야한다. 하지만 프레임워크 입장에서는 메소드에 들어올 인수의 타입이 무엇인지 알기 어렵다. 그렇다면 어떤 타입이 들어올지도 모르는 상황에서 프레임워크 코드는 어떻게 쓰여야할까? 이때 나오는 것이 바로 Reflection API
다.
Reflection API란! 구체적인 클래스 타입을 알지 못해도 그 클래스의 정보에 접근할 수 있게 해주는 자바 API다. 어떻게 알아내는지에 대해 알려면 JVM을 알아야한다.
Java Reflection API 동작 원리
JVM 영역에서 클래스와 클래스의 인스턴스가 저장되는 공간에 대해 생각해보자.
- 자바 코드는 컴파일러에 의해
.class
바이트 코드로 변환된다. 이 정보들이 클래스 로더를 통해Method Area
에 저장된다 - 이후에 class를 인스턴스화하고자 하면 Method Area에서 클래스 정보를 읽어와
Heap Area
에 인스턴스를 생성한다.
Reflection 은 런타임에 Method Area에 접근하여 클래스의 정보를 분석하고 수정한다!
Reflection 동적 로딩
클래스 정보를 얻어오는 방법은 크게 3가지가 있다
User tom = new User(10, "tom");
// 1. 인스턴스.getClass()
Class<? extends User> cls1 = tom.getClass();
// 2. 클래스.class
Class<? extends User> cls2 = User.class;
// 3. Class.forName() 동적 로딩
Class<?> cls3 = Class.forName("com.경로.경로.User");
다른 방법과 달리 Class.forName()
은 동적 로딩을 한다. 풀어 설명하자면 다른 메소드들은 컴파일 타임
에 메소드 영역의 클래스 파일과 바인딩한다. 그러나 Class.forName()은 런타임에 동적으로
클래스 파일과 바인딩한다.
동적 로딩이 왜 좋은가?
어떤 시스템이 여러 데이터베이스와 연동이 가능한다. 하지만 이를 컴파일 타임에 바인딩한다면 어떠한 db를 쓸지도 모르는 상황에서 모든 db 라이브러리를 컴파일하게 되어 비효율적이다.하지만 동적 로딩을 하게 된다면 필요한 db의 라이브러리만
런타임
에 바인딩하게 되므로 효율적이다.
Reflection 으로 생성자, 메서드, 필드 가져와보기
- getDeclaredxxx : 접근 제어자와 상관없이 상속한 메서드들을 제외하고 직접 클래스에서 선언한 값들을 가져온다.
- getxxx : 상속한 메소드를 포함하여 public 인 것들만 가져온다. 따라서 private 메서드는 조회하지 못한다.
private 으로 되어있는 것들은 접근을 허용하기 위해 setAccessible 로 true 해준다.
public class User {
public int age;
public String name;
public User(int age, String name) {
this.age = age;
this.name = name;
}
private User(int age) {
this.age = age;
this.name = "default";
}
private void sayHello() {
System.out.println("Hello my name is " + name);
}
}
1. 생성자 가져오기
// 동적 로딩
Class<?> cls = Class.forName("com.coffeecat.coffeecat.User");
Constructor<?> publicConsturctor = cls.getConstructor(int.class, String.class);
User tom = (User) publicConsturctor.newInstance(20, "tom");
System.out.println(tom);
Constructor<?> privateConstructor = cls.getDeclaredConstructor(int.class);
privateConstructor.setAccessible(true);
User privateUser = (User) privateConstructor.newInstance(10);
System.out.println(privateUser);
2. 메소드 가져오기
Constructor<?> privateConstructor = cls.getDeclaredConstructor(int.class);
privateConstructor.setAccessible(true);
User privateUser = (User) privateConstructor.newInstance(10);
System.out.println(privateUser);
Method sayHello = cls.getDeclaredMethod("sayHello");
sayHello.setAccessible(true);
sayHello.invoke(privateUser);
3. 필드 가져오기
Constructor<?> privateConstructor = cls.getDeclaredConstructor(int.class);
privateConstructor.setAccessible(true);
User privateUser = (User) privateConstructor.newInstance(10);
System.out.println(privateUser);
Field age = cls.getField("age");
Object o = age.get(privateUser);
System.out.println("age = " + o);
Reflection 은 어디에 쓰일까?
- Spring Framework Dependency Injection
- Spring Data JPA entity - jpa lazy loading, proxy
Reflection 예제 1 - dependency Injection
public UserServie userService() {
private UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
}
프레임워크가 짜일 때를 생각해보면 주입되어야하는 것이 UserRepository인지 모르는 상황이다. 이런 것들은 어쩔 수 없이 런타임에 결정되는 것이기 때문이다. 그래서 런타임에 객체 바인딩이 될 수 있도록 코드가 짜여있다.
//AbstractBeanFactory.class
String beanName = transformedBeanName(name);
Object beanInstance;
// Eagerly check singleton cache for manually registered singletons.
Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) {
if (logger.isTraceEnabled()) {
if (isSingletonCurrentlyInCreation(beanName)) {
logger.trace("Returning eagerly cached instance of singleton bean '" + beanName +
"' that is not fully initialized yet - a consequence of a circular reference");
}
else {
logger.trace("Returning cached instance of singleton bean '" + beanName + "'");
}
} beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, null);
}
bean 이름을 통해 Object beanInstance를 찾아왔다. 이제 이 Object를 이용하여 Reflection를 하는 것이다.
public static <T> T instantiateClass(Constructor<T> ctor, Object... args) throws BeanInstantiationException {
Assert.notNull(ctor, "Constructor must not be null");
try {
ReflectionUtils.makeAccessible(ctor);
if (KotlinDetector.isKotlinReflectPresent() && KotlinDetector.isKotlinType(ctor.getDeclaringClass())) {
return KotlinDelegate.instantiateClass(ctor, args);
}
else {
int parameterCount = ctor.getParameterCount();
Assert.isTrue(args.length <= parameterCount, "Can't specify more arguments than constructor parameters");
if (parameterCount == 0) {
return ctor.newInstance();
}
Class<?>[] parameterTypes = ctor.getParameterTypes();
Object[] argsWithDefaultValues = new Object[args.length];
for (int i = 0 ; i < args.length; i++) {
if (args[i] == null) {
Class<?> parameterType = parameterTypes[i];
argsWithDefaultValues[i] = (parameterType.isPrimitive() ? DEFAULT_TYPE_VALUES.get(parameterType) : null);
}
else {
argsWithDefaultValues[i] = args[i];
}
} return ctor.newInstance(argsWithDefaultValues);
}
}
}
BeanUtils 클래스의 코드이다. Constructor 메소드를 가져오고 이것으로 newInstance() 를 하는 것을 볼 수 있다.
Reflection 예제 2 - JPA lazy loading (기본 생성자가 필요한 이유)
기본 생성자는 자동으로 생성될 수도?
java compiler는 생성자 코드가 없을 경우 컴파일러가 기본 생성자를 자동으로 생성한다. 그러나 생성자 코드가 1개라도 작성되어 있다면, 컴파일러는 기본생성자가 없다고 하더라도 기본 생성자를 자동으로 생성하지 않는다.
JPA 엔티티에는 기본 생성자가 필요하다.
리플렉션 API 와 관련있는 부분이다. JPA는 컴파일 타임이 아니라 런타임에 데이터베이스 값을 객체 필드에 주입해야한다. 이 과정에서 Reflection API를 사용하게 된다.
그러나 Reflection API가 접근제어자 상관없이 모든 정보들을 가져올 수 있지만 딱 하나 생성자의 인자 정보
를 가져올 수 없다. 따라서 기본 생성자가 있어야 Reflection API를 사용할 수 있는 것이다.
(착각하지 말자. 잘못된 내용이다.)
참고 https://colour-my-memories-blue.tistory.com/16
- 인자 정보를 가져올 수 없던 것은 java7 까지의 이야기다. 실제로는 아주 잘 가져와진다. 대신 인자의 이름까지 가져오려면 컴파일 옵션을 넣어야한다.
- java7이 아니지만 기본 생성자가 필요한 이유? 결국에는 기본 생성자로 객체 만들고 set으로 필드를 넣어주는 것이 제일 간편한 방법이기 때문이다.
JPA 엔티티의 기본 생성자는 private이면 안된다.
Proxy 객체와 관련이 있다. 엔티티 지연 로딩을 사용할 때 프록시 객체를 사용하는데 프록시 객체는 원본 엔티티를 상속하기 때문에 Protected 혹은 Public 기본 생성자가 필요하다.
Reflection 단점
- 일반 메서드 호출보다 성능이 떨어진다 : 동적으로 클래스를 생성하기 때문에 JVM 컴파일러의 최적화 도움을 받을 수 없다.
- 컴파일 시 타입 체크 불가능 : 런타임에 클래스의 정보가 얻어지므로 컴파일 시점에 확인이 불가능
- 추상화 파괴 : 리플렉션은 접근 제어자를 모두 무시해버린다. (setAccessible true로 해버리면 모두 접근 가능) 즉 private 접근 제어자도 모두 접근 가능해지게 된다.
'📗Java' 카테고리의 다른 글
[Java] Arrays.sort() 어떤 정렬 알고리즘을 쓸까? (2) | 2024.04.30 |
---|---|
[모던자바인액션] Optional (0) | 2023.10.23 |
[모던자바인액션] 람다 표현식 (1) | 2023.09.14 |
[모던자바인액션] 동작 파라미터화 코드 전달하기 (0) | 2023.09.11 |
[Java] Garbage Collection (0) | 2023.09.07 |