자바 Reflection이란?

Solomon Maeng
msolo021015
Published in
8 min readApr 1, 2019

많은 입문용 자바 서적에서 잘 다루지 않는 Reflection이라는 개념에 대해서 알아보려고 합니다.

자바의 Reflection은 JVM에서 실행되는 애플리케이션의 런타임 동작을 검사하거나 수정할 수 있는 기능이 필요한 프로그램에서 사용됩니다.

쉽게 말하자면, 클래스의 구조를 개발자가 확인할 수 있고, 값을 가져오거나 메소드를 호출하는데 사용됩니다.

Reflection을 사용하는 기술을 나열하자면, 우리가 잘 아는 스프링 프레임워크, 대표적 ORM 기술인 하이버네이트, jackson라이브러리 등에 사용됩니다.

Reflection을 사용해서 스프링에서는 런타임 시에 개발자가 등록한 빈을 애플리케이션에서 가져와 사용할 수 있게 되는 것입니다.

그럼 예제를 통해서 Reflection이 어떤 것인지 알아보도록 하겠습니다.

예제 코드는 깃허브에 있습니다.

Object.getClass()를 통해 클래스의 정보를 로드합니다.

//임의의 클래스를 가져오는 방법
Class c = "foo".getClass();
System.out.println(c); //class java.lang.String
//Array는 객체이므로 Array 인스턴스에서 클래스 정보를 로드할 수 있습니다.
byte[] b = new byte[1024];
Class c1 = b.getClass();
System.out.println(c1); //class [B
Set<String> s = new HashSet<>();
Class c2 = s.getClass();
System.out.println(c2); //class java.util.HashSet

.class 문법을 사용합니다.

//.class 문법
boolean bl;
Class c3 = bl.getClass(); //컴파일 에러 발생
Class c4 = boolean.class;

이처럼 boolean형은 원시 유형이기 때문에, b1.getClass()를 사용하면 컴파일 에러가 발생합니다.

Class c5 = java.io.PrintStream.class;
System.out.println(c5); //class java.io.PrintStream
Class c6 = int[][].class;
System.out.println(c6); //class [[I

변수 c5는 java.io 유형에 해당하는 PrintStream이 됩니다. 다차원 배열 또한 .class 구문이 사용가능합니다.

Class.forName() 문법을 사용합니다.

//아래와 같이 패키지 명으로 클래스를 로드할 수 있습니다.
Class c7 = Class.forName("ko.maeng.reflection.ReflectionApplication");
Class doubleArray = Class.forName("[D"); //class [D
Class stringArray = Class.forName("[[Ljava.lang.String;"); //class [[Ljava.lang.String;

변수 doubleArray는 double형 배열의 클래스를 로드한 것과 같고, 변수 stringArray는 2차원 문자열 배열의 클래스를 로드한 것과 같습니다.

TYPE Field를 통한 원시형 클래스 반환 방법.

Class c8 = Double.TYPE;     //double
Class c9 = Void.TYPE; //void

Method를 활용한 클래스 반환 방법.

  • class.getSuperClass() : 슈퍼 클래스를 반환합니다.
  • class.getClass() : 상속된 클래스를 포함하여 모든 공용 클래스, 인터페이스 및 열거형을 반환합니다.
  • class.getDeclaredClass() : 명시적으로 선언된 모든 클래스 및 인터페이스, 열거형을 반환합니다.
  • class.getDeclaringClass() : 클래스에 구성된 클래스(명시적으로 선언된)를 반환합니다.
  • class.getEnclosingClass() : 클래스의 즉시 동봉된 클래스를 반환합니다.

클래스의 정보를 로드해 봤으니, c가 A의 인스턴스인지 확인합니다.

public static void main(String[] args) {
try{
Class c = Class.forName("ko.maeng.reflection.A");
boolean b = c.isInstance(new Integer(22));
System.out.println(b); //false
boolean b1 = c.isInstance(new A());
System.out.println(b1); //true
} catch (ClassNotFoundException e){
e.printStackTrace();
}
}

임의의 클래스에서 메소드 정보를 반환해봅니다.

출력 결과

생성자(Constructor)에 대한 정보를 반환합니다.

출력 결과

클래스의 필드(property)를 찾아서 반환해봅시다.

출력 결과

마지막으로 필드의 값을 변경해봅니다.

출력 결과

마지막으로 Reflection의 특징과 단점을 알아보겠습니다.

  • 확장성 특징 : 애플리케이션은 정규화된 이름을 사용하여 확장성 객체의 인스턴스를 생성하여 외부 사용자 정의 클래스를 사용할 수 있습니다.
  • 클래스 브라우저 및 시각적 개발 환경을 제공합니다 : 클래스 브라우저는 클래스의 Method, Property, Constructor를 열거할 수 있어야 합니다. 시각적 개발 환경은 개발자가 올바른 코드를 작성하는데 도움이 되도록 Reflection에서 사용할 수 있는 형식 정보를 사용하면 도움이 됩니다.
  • 디버거 및 테스트 도구입니다 : 디버거는 개인 Property, Method, Constructor를 검사할 수 있어야 합니다. 테스트 장치는 Reflection을 사용하여 클래스에 정의된 발견 가능한 세트 API를 체계적으로 호출하여 테스트에서 높은 수준의 코드 커버리지를 보장합니다.

Reflection의 주의사항 및 단점.

Reflection은 강력한 도구이지만, 무분별하게 사용해서는 안됩니다. Reflection을 사용하지 않고 수행 가능하다면, 사용하지 않는 것이 좋습니다. Reflection을 통해 코드에 접근할 때는 다음 사항을 염두에 두어야 합니다.

  • Performance의 오버헤드 : Reflection에는 동적으로 해석되는 유형이 포함되므로, 특정 JVM 최적화를 수행할 수 없습니다. 따라서 Reflection 작업이 비 Reflection 작업보다 성능이 떨어지며, 성능에 민감한 애플리케이션에서 자주 호출되는 코드엔 사용하지 않아야 합니다.
  • 보안 제한 사항 : Reflection에는, 시큐리티 매니저의 실행 시에 존재하지 않는 실행 시 액세스 권한이 필요합니다. 이것은 애플릿과 같이 제한된 보안 컨텍스트에서 실행되어야 하는 코드에 대한 중요한 고려 사항입니다.
  • 캡슐화를 저해할 수 있습니다 : Reflection은 private한 Property및 Method에 액세스하는 것과 같이 비 Reflection 코드에서 작동하지 않는 코드를 수행할 수 있으므로, Reflection을 사용하면 예기치 않은 부작용이 발생하여 코드 기능이 저하되고 이식성이 손상될 수 있습니다. 또한 Reflection은 추상화를 깨뜨려 플랫폼 업그레이드 시 동작이 변경될 수 있습니다.

참고 자료 : https://docs.oracle.com/javase/tutorial/reflect/

--

--