[Java] Reflection

NHJ·2022년 4월 14일
0

Reflection이란?

구체적인 클래스 타입을 알지 못해도 그 클래스의 정보(메서드, 타입, 변수 등등)에 접근할 수 있게 해주는 자바 API다.

public class Car {
    private final String name;
    private int position;

    public Car(String name, int position) {
        this.name = name;
        this.position = position;
    }

    public void move() {
        this.position++;
    }

    public int getPosition() {
        return position;
    }
}

위와 같이 Car class가 있다.

public static void main(String[] args) {
    Object obj = new Car("foo", 0);
    obj.move();    // 컴파일 에러 발생 java: cannot find symbol
}

자바의 다형성 덕분에 Object 타입에 Car 객체를 대입할수 있다.
하지만 obj라는 이름의 객체는 Car 클래스의 move 메소드를 사용할 수 없다.
왜냐하면 자바는 컴파일타임에 타입이 결정되기 때문에 obj 객체는 Object로 타입이 결정되었고 따라서 Object 클래스의 인스턴스 변수와 메소드만 사용가능하다.

생성된 obj 객체는 Object 클래스라는 타입만 알 뿐, Car 클래스라는 구체적인 타입은 모른다. 결국 컴파일러가 있는 자바는 구체적인 클래스를 모르면 해당 클래스의 정보에 접근할 수 없다는 것을 알 수 있다.

이를 Reflection API를 사용하여 해결할 수 있다.

public static void main(String[] args) throws Exception {
    Object obj = new Car("foo", 0);
    Class carClass = Car.class;
    Method move = carClass.getMethod("move");

    // move 메서드 실행, invoke(메서드를 실행시킬 객체, 해당 메서드에 넘길 인자)
    move.invoke(obj, null);

    Method getPosition = carClass.getMethod("getPosition");
    int position = (int)getPosition.invoke(obj, null);
    System.out.println(position);
    // 출력 결과: 1
}

move 메소드가 실행되고 0으로 초기화했던 Car 클래스 인스턴스 변수 position이 1로 출력되는 걸 확인할 수 있다.

Reflection API로 구체적인 클래스 Car 타입을 알지 못해도 move 메서드에 접근한 것이다.

Class carClass2 = Class.forName("Car");

위의 예제처럼 클래스의 이름만으로도 해당 클래스의 정보를 가져올 수 있다.
다시 말해서 Reflection API는 클래스의 이름만 가지고도 생성자, 필드, 메소드 등등 해당 클래스에 대한 거의 모든 정보를 가져올 수 있는 마법 같은 API라는 것이다.

어떻게 가능할까?

자바에서는 JVM이 실행되면 사용자가 작성한 자바 코드가 컴파일러를 거쳐 바이트 코드로 변환되어 static 영역에 저장된다. Reflection API는 이 정보를 활용한다. 그래서 클래스 이름만 알고 있다면 언제든 static 영역을 뒤져서 정보를 가져올 수 있는 것이다.

어디에 활용할 수 있을까?

위에서 살펴봤던 예제 코드를 보면 멀쩡한 Car 객체를 Object 타입으로 생성하고 있다. 실제로 우리가 코드를 작성할 때는 예제와 같이 작성하지 않는다. 그러므로 우리가 코드를 작성하면서 Reflection을 활용할 일은 거의 없다. 구체적인 클래스를 모를 일이 거의 없기 때문이다.

Reflection은 애플리케이션 개발보다는 프레임워크나 라이브러리에서 많이 사용된다. 프레임워크나 라이브러리는 사용자가 어떤 클래스를 만들지 예측할 수 없기 때문에 동적으로 해결해주기 위해 Reflection을 사용한다.

실제로 intellij의 자동완성, jackson 라이브러리, Hibernate 등등 많은 프레임워크나 라이브러리에서 Reflection을 사용하고 있다.

Spring Framework에서도 Reflection API를 사용하는데 대표적으로 Spring Container의 BeanFactory가 있다. Bean은 애플리케이션이 실행한 후 런타임에 객체가 호출될 때 동적으로 객체의 인스턴스를 생성하는데 이때 Spring Container의 BeanFactory에서 리플렉션을 사용한다.

Spring Data JPA 에서 Entity에 기본 생성자가 필요한 이유도 동적으로 객체 생성 시 Reflection API를 활용하기 때문이다. Reflection API로 가져올 수 없는 정보 중 하나가 생성자의 인자 정보이다. 그래서 기본 생성자가 반드시 있어야 객체를 생성할 수 있는 것이다. 기본 생성자로 객체를 생성만 하면 필드 값 등은 Reflection API로 넣어줄 수 있다.

Reflection의 주의사항 및 단점

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

Performance의 오버헤드: Reflection에는 동적으로 해석되는 유형이 포함되므로, 특정 JVM 최적화를 수행할 수 없다. 따라서 Reflection 작업이 비 Reflection 작업보다 성능이 떨어지며, 성능에 민감한 애플리케이션에서 자주 호출되는 코드엔 사용하지 않아야 한다.

캡슐화 저해: Reflection은 private한 Property및 Method에 액세스하는 것과 같이 비 Reflection 코드에서 작동하지 않는 코드를 수행할 수 있으므로, Reflection을 사용하면 예기치 않은 부작용이 발생하여 코드 기능이 저하되고 이식성이 손상될 수 있다. 또한 Reflection은 추상화를 깨뜨려 플랫폼 업그레이드 시 동작이 변경될 수 있다.

[참고]

profile
화이팅!

0개의 댓글