공부방/JAVA

자바 리플렉션(Reflaction)

EVO. 2023. 9. 28. 19:29

어노테이션의 개념을 공부하던 중 리플렉션이라는 개념이 어노테이션을 커스텀 할 때 꼭 필요하다는 것을 알게 돼 이번 기회를 통해 정리해본다.

리플렉션 이란?

리플렉(reflect)의 어원 : "반사하다/비치다" 

리플렉션은 밑에 사진처럼 클래스를 직접 조작하는 것이 아니라 거울에 반사된 사람을 보고 클래스를 검사하고 조작하는 기술이라고 보면 된다. 

클래스는 자바 소스코드라고 보면 되겠다

리플렉션의 정의를 보면 런타임에 클래스와 인터페이스등을 검사하고 조작할 수 있는 기능 이라고 적혀있는데

JVM 아키텍처 관점에서 이해하면, 런타임 동안 JVM의 Method Area에는 정적변수를 포함한 모든 클래스 수준 데이터가 저장된다. 여기서 리플렉션이란 거울에 반사되는 사람이 곧 Method Area라고 보면 되고 이 영역을 보면서 클래스를 조작하고 검사한다고 보면 되겠다.

 

이제 여기서 드는 의문은 어떻게 Method Area를 보고 클래스 정보를 보고 조작을 할까 이다. 이는 다시 말해서 리플렉션을 어떻게 사용하나 이다.

Class 클래스

  • Class 클래스의 인스턴스는 Java 애플리케이션에서의 interface라고 부르고 또는 클래스들을 제시하는 대표적인 리플렉션이다.
  • Class 클래스가 reflection 할 수 있는 클래스들은 거의 다 (Enum, array, annotation(인터페이스포함), primitive types, annotation, void)라고 해도 무방할 정도이다.
  • Class는 public 생성자가 없다. 클래스로더에 의해 .class 파일들이 로드되고 링킹하고 초기화한다음 JVM에 의해 자동으로 Class 클래스 인스턴스를 생성해서 Method Area에 집어넣는다. 클래스를 로드할때 쓰는 메서드가 세가지가 있는 것으로 보인다. deplecated된건 밑줄로 그었다.
    (
    1. ClassLoader::defineClass
    2. java.lang.invoke.MethodHandles.Lookup::defineClass 
    3. java.lang.invoke.MethodHandles.Lookup::defineHiddenClass
  • Class 클래스의 메서드는 한 클래스 또는 인터페이스의 많은 특성들을 노출한다

Class 객체 획득 방법

(1)은 class 리터럴을 사용해 클래스 객체를 얻는다

(2)는 getClass() 메서드를 이용해 Class 객체를 얻는다

(3)은 FQCN(Fully Quallified Class Name)를 전달하여 해당 경로와 대응하는 클래스에 대한 Class 클래스의 인스턴스를 얻는 방법이다

getXXX()  상속받은 클래스와 인터페이스를 포함하여 모든 public 요소를 가져온다

getMethods() 예시

출력결과 : 상속받은 Object 객체의 메서드와 해당 Member클래스의 public 메서드가 모두 출력되고 있다. 

getFields() 예시

먼저 Member클래스에 public 필드를 추가했다. 

    public final static String hi = "HI";

그다음 출력했더니 private을 제외한 public 필드만 출력되었다 

getAnnotations()

@Deprecated를 붙인 Member 클래스는 다음과 같이 어노테이션의 정보가 출력된다.

 

반면, getDeclaredXXX()  상속받은 클래스와 인터페이스를 제외하고 해당 클래스에 직접 정의된 내용만 가져온다. 또한 접근 제어자와 상관없이 요소에 접근할 수 있다. 예를 들어 getDeclaredMethods() 는 해당 클래스에만 직접 정의된 private, protected, public 메소드를 전부 가져온다.

 

private 필드에 접근

getDeclaredFields()는 private 필드에 접근할 수 있다. 다만 특정 인스턴스의 필드 값에 접근하려면 다음과 같은 setAccessible(true)가 필요하다

출력 결과

심지어 private 필드를 set메소드를 이용하여 Setter 없이도 강제로 필드의 값을 변경할 수 있다

"name"이라는 필드를 가져왔을 때 먼저 private을 접근하기 위해 setAccessible(true)로 해주고 필드값을 바꿨다

Constructor 

private이 필드 뿐 아니라 메서드와 생성자까지 모두 private일때 객체를 생성하는 방법이다.

 

19번째 줄 부터 보면

  • 먼저 Profile Class 객체를 생성한다.
  • 해당 Class 객체를 사용해서 생성자를 Constructor 타입으로 가져오는데 생성자에는 파라미터가 필요하므로  getDeclaredConstructor 메소드에 생성자 파라미터에 대응하는 타입을 전달하면 된다. 
  • 23번째 줄에서 newInstance로 객체를 생성하는데 private생성자로 객체를 생성할 때  java.lang.IllegalAccessException이 발생하므로 setAccessible(true)로 해결한다.
  • jdk 17부터 제네릭 T가 적용되면서 Class객체를 생성하면서 이미 Profile 객체를 전달했기 때문에 더이상 타입 캐스팅이 필요없다
  • 그렇게 만들어진 profile객체의 해당 private 메서드를 실행시키기 위해 먼저 getDeclaredMethod()로 Profile 클래스의 메서드 객체를 받아온다
  • 그리고 setAccessible(true)로 접근문제를 해결하고 invoke메서드로 profile객체를 인자로 전달하여 메서드를 실행시킨다.

 

Reflection의 단점

리플렉션의 단점은 매우 많다

출처 : 헙크 테코톡 https://www.youtube.com/watch?v=RZB7_6sAtC4&t=221s&ab_channel=%EC%9A%B0%EC%95%84%ED%95%9C%ED%85%8C%ED%81%AC

따라서 애플리케이션 개발자가 사용하는 것은 되도록 지양해야 하며 정말 필요한 곳에서만 사용해야 한다. 다만 프레임워크나 라이브러리 제공자는 런타임에 사용자와 상호작용하면서 기능을 제공하고 싶은 경우 한정적으로 사용하면 된다. 

 

 

 

참고