[Java] Reflection - Spring DI는 어떻게 척척 필요한 의존성을 주입할 수 있었나?

·2024년 1월 18일
0

Java

목록 보기
1/5
post-thumbnail

Reflection

런타임에 클래스와 인터페이스 등을 검사하거나, 조작하는 기능이다. JVM 은 개발자가 작성한 코드를 기반으로, JVM 메모리 영역에 클래스, 인터페이스, 메서드 등의 바이트 정보를 보관한다. 이를 통해 실제 클래스로부터 투영된 정보 Reflection 를 바탕으로 런타임 환경에서 의도된 제어를 진행할 수 있다.

사용처

주로 프레임워크나 개발도구의 주요 기능 설계들이 대부분 Reflection 이 적용되었다.

  • Spring
  • JPA, Hibernates
  • JUnit, Mockito
  • Jackson, GSON 등의 JSON Serialization
  • Intellij 의 자동완성 기능
  • Annotation 설계 및 적용

Reflection 사용하기

Class

Reflection을 활용하여 Class 정보를 불러오는 방법은 다음과 같다.

public static void main(String[] args) throws Exception {
	  // {클래스 타입}.class
    Class car = Car.class;
    
    // Class.forName({전체 도메인 이름})
    Class car = Class.forName("com.reflection.test.Car");
    // class.getName() -> com.reflection.test.Car
 
    // {인스턴스}.getClass();
		Car c = new Car();
    Class realCar = c.getCalss();
}

getXXX, getDeclaredXXX

  • getXXX() : 상위 클래스와 상위 인테페이스에서 상속한 정보를 포함하여 접근 제어자가 public 으로 설정된 정보들을 모두 가져온다.

    ex ) getMethods(), getContructor(), getField ...

  • getDeclaredXXX() : 상속한 정보를 제외하고 직접 클래스에서 설정한 정보들을 접근 제어자에 관계없이 모두 가져온다.

    ex ) getDeclaredMethods(), getDeclaredContructor(), getDeclaredField ...

생성자 조회, 인스턴스 생성

getContructor(), getDeclaredConstructor() 를 통해 Constructor 타입의 객체로 생성자를 불러올 수 있다. Constructor 객체는, newInstance() 와 같이 생성자 정보를 기반으로 인스턴스를 생성하는 것이 가능하다.

public static void main(String[] args) throws Exception {
    Class car = Class.forName("com.reflection.test.Car");
    
    // 기본 생성자 가져오기
    Constructor constructor = car.getDeclaredConstructor();
    
    // String 인자를 받는 생성자 가져오기
    Constructor constructor = car.getDeclaredConstructor(String.class);
    
    // 모든 생성자 가져오기
    Constructor constructors[] = car.getDeclaredConstructors();
    
    // public 생성자만 가져오기
    Constructor constructors[] = car.getConstructors();
    
    // 생성자를 이용한 인스턴스 생성
    Car realCar = constructor.newInstance();
}

더불어 접근제어자가 public 이 아닌 경우는, setAccessable(true) 를 통해, 해당 객체 정보에 접근할 수 있다.

// private으로 설정된 생성자 가져오기
Constructor constructor = car.getDeclaredConstructor();

// Object obj = constructor.newInstance(); // java.lang.illegalAccessException
constructor.setAccessible(true);
Object obj = constructor.newInstance(); // ok!

field

클래스의 필드 정보에 대하여, Field 타입 객체로 불러올 수 있다. 이를 통해 필드의 접근 제어자, 타입, 이름, 값 등을 조회할 수 있다. Field 타입 객체는 get(), set() 함수를 가지고 있어서, 해당 필드가 가지고 있는 값을 조회하거나, 새롭게 변경할 수 있다.

Class car = Class.forName("com.reflection.test.Car");
    
// name으로 시작하는 필드 불러오기
// Field field = car.getDeclaredField("name");
    
// car, car의 상속 객체를 포함하여 이름이 name인 field를 가져오기
Field field = car.getField("name");

// car 객체에 선언된 모든 field 가져오기
Field[] fields = car.getDeclaredFields();

field 조회, field 변경

만약 필드의 접근제어자가 private 인 경우에는 getDeclaredXXX()setAccessible(true) 를 설정해줘야한다.

Class class = Class.forName("com.reflection.test.Car");
Constructor constructor = class.getConstructor()
Car car = constructor.newInstance()
    
Field field = car.getField("name");
field.set(car, "아반떼");

field = car.getDeclaredField("name");
field.setAccessible(true);
field.set(car, "아반떼");

Method

getMethod(), getDeclaredMethod() 를 활용하여 클래스의 메서드 정보를 Method 라는 객체로 불러올 수 있다. 이를 통해 메서드의 접근 제어자, 리턴 타입, 이름, 인자 정보, 인자 타입 등의 정보를 가져올 수 있다. Method 객체는 invoke() 함수를 가지고 있어, 해당 메서드를 실행하는 것도 가능하다.

Class car = Class.forName("com.reflection.test.Car");
Class realCar = car.newInstance();

// 인자가 없는 method 가져오기
Method method = car.getMethod("move");
method.invoke(realCar, /*인자*/);

// String 인자를 가진 method 가져오기
method = car.getDeclaredMethod("move", String.class);

method.setAccessible(true);
method.invoke(realCar, "XXX");

Reflection 특징

라이브러리, 프레임워크 개발에 효과적

라이브러리, 프레임워크 개발의 경우 사용자가 어떤 클래스를 사용하는지 파악할 수 없다. 이를 Class , Constructor , Field , Method 등의 인터페이스 타입으로 추상화하여 적용시킬 수 있기 때문에, 개발이 용이해지는 장점이 있다.

단점들

  • 런타임 환경에서 필요한 정보를 분석하기 때문에, JVM의 코드 최적화에서 제외가 되어, 일반 메서드 대비 성능이 좋지 않다.
  • 컴파일 시점에서 타입 체크 기능을 활용할 수 없다. (에러를 파악하기 어렵다.)
  • 접근 제어자에 상관없이 내부에 접근하여 정보를 가져오거나, 업데이트가 가능하다. 이러한 점들이 객체 지향의 설계 질서를 무너뜨린다. (정보은닉, 불변성 등을 파괴)

요약

Java Reflection 은 라이브리러, 프레임워크를 개발하기에 매우 좋은 수단이며, 실제 Java 진영에서의 대표적인 라이브러리의 주요 기능 모두, Java Reflection 을 통해 개발이 되었다.

그러나, Java Reflection 을 사용하며 나타나는 단점도 많기 때문에, 일반적인 개발의 경우라면 사용을 지양해야하며, 프레임워크, SDK 개발에서 꼭 필요한 경우에만 Java Reflection 을 사용하자.

참고
[10분 테코톡] 헙크의 자바 Refletion
[10분 테코톡] 파랑, 아키의 리플렉션
Reflection은 무엇이고 언제/어떻게 사용하는 것이 좋을까?

profile
새로운 것에 관심이 많고, 프로젝트 설계 및 최적화를 좋아합니다.

0개의 댓글