우아한테크코스 테코톡
파랑, 아키의 리플렉션
카테고리 : 우아한테크코스 테코톡
파랑, 아키의 리플렉션
리플렉션이란?
- 스프링은 어떻게 실행시점 에 빈을 주입할 수 있는 걸까?
- JPA의 Entity는 왜 꼭 기본 생성자 를 가져야만 할까 ?
- 자바 세상에서는 실체는 Class 이고 거울은 JVM 메모리 영역이다.
- 클레스 데이터는 메소드 영역에 인스턴스 정보는 힙 영역에 저장된다.
- 리슬렉션 기능을 지원하지 않는 언어도 있다.
- C, C++, Pascal
리플렉션의 기능
- 리플렉션의 기본은 Class
Class
- Class 는 실행중인 자바 어플리케이션의 클래스와 인터페이스의 정보를 가진 클래스
- public 생성자가 존재하지 않는다.
- Class 객체는 JVM에 의해 자동으로 생성된다.
Class 기능들
- 클래스에 붙은 어노테이션 조회
- 클래스 생성자 조회
- 클래스 필드 조회
- 클래스 메서드 조회
- 부모 클래스, 인터페이스 조회
- 등등
Class 가져오기
- {클래스 타입}.class
Class<?> clazz = Test.class;
- {인스턴스}.getClass()
Test test = new test("test");
Class<?> clazz = test.getClass();
3.Class.forName(“{전체 도메인 네임}”)
Class<?> clazz = Class.forName("org.test.Test");
Class의 메서드 사용 시 주의점
- getXXX 와 getDeclaredXXX를 잘 구분해서 사용해야 한다.
- getMethods
- 상위 클래스와 상위 인터페이스에서 상속한 메서드를 포함하여 public인 메서드를 모두 가져온다.
- getDeclaredMethods
- 접근 제어자와 관계 없이 상속한 메서드를 제외하고 직접 클래스에서 선언한 메서들을 모두 가져온다.
생성자를 통한 객체 생성
- 생성자의 파라미터로 구분하여 클래스에 선언된 생성자를 가져올 수 있다.
Class<?> clazz = Class.forName("org.test.Test");
Constructor<?> constructor1 = clazz.getDeclaredConstructor();
Constructor<?> constructor2 = clazz.getDeclaredConstructor(String.class);
Constructor<?> constructor3 = clazz.getDeclaredConstructor(String.class, int.class);
Object test = constructor1.newInstance(); //생성자의 접근제어자가 private 라서 접근이 불가
- 접근제어자가 public이 아닌 경우 setAccessible 메서드를 이용하면 접근할 수 있다.
constructor1.setAccessible(true);
Object test = constructor1.newInstance();
Object test = constructor2.newInstance("test");
Object test = constructor3.newInstance("test", 5);
필드 정보 조회
- 필드의 접근제어자, 타입, 네임, 값 등의 정보를 조회할 수 있다.
Object test = constructor3.newInstance("test", 5);
Field[] fields = clazz.getDeclaredFields();
for(Field field : fields){
field.setAccessible(true);
System.out.println(field);
System.out.println("value : " + field.get(test));
}
필드 값 변경
- private 필드의 값도 변경할 수 있다.
Field field = clazz.getDeclaredField("name");
field.setAccessible(true);
System.out.println("기존 : " + field.get(test));
field.set(test, "test2")
System.out.println("변경 : " + field.get(test));
메서드 정보 조회
- 메서드의 접근제어자, 리턴 타입, 네임, 파라미터 타입, 등의 정보를 가져올 수 있다.
Method[] methods = clazz.getDeclaredMethods();
for(Method method : methods){
method.setAccessible(true);
System.out.println(method);
}
메서드 호출
- private 메서드도 호출할 수 있다.
Method method = clazz.getDeclaredMethod("testing", String.class, int.class);
method.setAccessible(true);
method.invoke(dog, "test", 5)
리플렉션이 사용되는 곳
- 리플렉션을 평소에 사용할 일이 없다 보니 생소할 수 있는데 실은 정말 많은 곳에서 사용되고 있다.
리플렉션을 왜 쓸까?
- 리플렉션은 주로 프레임워크나 라이브러리에서 많이 사용한다.
- 우리가 직접 코딩을 할때는 객체의 타입을 모르는 일이 거의 없다. 프레임워크나 라이브러리에서는 사용자가 생성한 객체가 어떤 타입인지 컴파일 시점까지는 알 수가 없다. 이러한 문제를 동적으로 해결하기 위해 리플렉션을 사용
리플렉션이 사용되는 곳
- JPA
- Jackson
- Mockito
- JUnit
- 인텔리제이의 자동완성 기능
많은 프레임워크나 라이브러리에서는 객체에 기본생성자 가 왜 필요할까?
- 기본 생성자로 객체를 생성하고, 필드를 통해 값을 넣어주는 것이 가장 간단한 방법 이기 때문이다.
- 기본 생성자가 없다면 생성자의 종류가 많은 경우 어떤 생성자를 사용할지 고르기 어렵다.
- 생성자에 로직이 있는 경우 원하는 값을 바로 넣어줄 수 없다.
- 파라미터들의 타입이 같은 경우 필드와 이름이 다르면 값을 알맞게 넣어주기 힘들다.
어노테이션
- 리플렉션을 통해 클래스나 메서드, 파라미터 정보를 가져온다.
- 리플렉션의 getAnnotation(s), getDeclaredAnnotation(s) 등의 메서드를 통해 원하는 어노테이션이 붙어 있는지 확인한다.
- 어노테이션이 붙어 있다면 원하는 로직을 수행한다.
리플렉션의 단점
- 일반 메서드 호출보다 성능이 훨씬 떨어진다.
- Reflection API 는 컴파일 시점이 아니라 런타임 시점에서 클래스를 분석한다.
- JVM을 최적화할 수 없기 때문에 성능저하 발생
- 메서드 호출을 10만번 반혹한 결과
- 일반메서드 호출시 : 7ms
- 리플렉션을 사용했을 경우 : 170ms
- 24배 차이가 발생 했다.
- Reflection API 는 컴파일 시점이 아니라 런타임 시점에서 클래스를 분석한다.
- 컴파일 시점에서 타입 체크 기능을 사용할 수 없다.
- 리플렉션은 런타임 시점에 클래스 정보를 알게 된다.
- 코드가 지저분하고 장황해진다.
- 내부를 노출해서 추상화를 파괴한다.
- 리플렉션을 사용하면 접근할 수 없는 필드나 메서드에도 접근이 가능하고 , 모든 클래스의 정보를 알게 된다.
- 추상화를 파괴하고 따라서 불변성 또한 지킬 수가 없게 된다.
- 리플렉션을 사용하면 접근할 수 없는 필드나 메서드에도 접근이 가능하고 , 모든 클래스의 정보를 알게 된다.