자바의 리플렉션(Reflection)은 무엇인가?

Jake Seo·2021년 6월 30일
1

자바 잡지식

목록 보기
7/17

정의

  • 리플렉션(Reflection)은 메소드, 클래스, 인터페이스의 행위를 런타임에 검사하거나 수정하기 위해 사용되는 API이다.
  • 리플렉션이라는 이름은 같은 시스템에서 다른 코드를 검증하는 것이 가능함을 표현하기 위해 사용되는 이름이다.

설명

  • java.lang.reflect 패키지 아래에서 리플렉션에 필요한 클래스들이 제공된다.
  • 리플렉션은 오브젝트를 사용함으로써 실행될 수 있는 클래스의 메소드들과 오브젝트가 속한 클래스에 대한 정보를 우리에게 제공한다.
  • 리플렉션을 통해 우리는 접근 제어자에 상관없이 런타임에 메소드를 호출할 수 있다.

메소드들

  • .getClass(): 오브젝트가 속한 클래스의 이름을 얻을 수 있다.
  • .getConstructors(): 오브젝트가 속한 클래스의 public 생성자를 얻을 수 있다.
  • .getMethods(): 오브젝트가 속한 클래스의 공개된 메소드들을 얻을 수 있다.

위와 같은 메소드들을 통해서 런타임에 해당 클래스 혹은 메소드의 행위를 검증 혹은 수정할 수 있다.

테스트

public class ReflectionTest {
    private String str;

    public ReflectionTest() {
        str = "ReflectionTest";
    }

    public void publicMethod() {
        System.out.println("str = " + str);
    }

    public void publicMethodWithArg(int arg) {
        System.out.println("arg = " + arg);
    }

    private void privateMethod() {
        System.out.println("(private) str = " + str);
    }

    @Test
    public void reflectionTest() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        ReflectionTest reflectionTest = new ReflectionTest();

        Class reflectionTestClass = reflectionTest.getClass();
        System.out.println("reflectionTestClass.getName() = " + reflectionTestClass.getName());

        Constructor constructor = reflectionTestClass.getConstructor();
        System.out.println("constructor.getName() = " + constructor.getName());

        Method publicMethodWithArg = reflectionTestClass.getDeclaredMethod("publicMethodWithArg", int.class);
        publicMethodWithArg.invoke(reflectionTest, 15);
		
        // 접근 제어자에 상관없이 메소드를 호출하는 방법
        Method privateMethod = reflectionTestClass.getDeclaredMethod("privateMethod");
        privateMethod.setAccessible(true);
        privateMethod.invoke(reflectionTest);


        Method[] methods = reflectionTestClass.getMethods();
        for (Method method : methods) {
            System.out.println("reflectionTestClass method = " + method);
        }
    }
}

테스트 결과

reflectionTestClass.getName() = reflection_test.ReflectionTest
constructor.getName() = reflection_test.ReflectionTest
arg = 15
(private) str = ReflectionTest
reflectionTestClass method = public void reflection_test.ReflectionTest.publicMethodWithArg(int)
reflectionTestClass method = public void reflection_test.ReflectionTest.reflectionTest() throws java.lang.NoSuchMethodException,java.lang.reflect.InvocationTargetException,java.lang.IllegalAccessException
reflectionTestClass method = public void reflection_test.ReflectionTest.publicMethod()
reflectionTestClass method = public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
reflectionTestClass method = public final void java.lang.Object.wait() throws java.lang.InterruptedException
reflectionTestClass method = public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
reflectionTestClass method = public boolean java.lang.Object.equals(java.lang.Object)
reflectionTestClass method = public java.lang.String java.lang.Object.toString()
reflectionTestClass method = public native int java.lang.Object.hashCode()
reflectionTestClass method = public final native java.lang.Class java.lang.Object.getClass()
reflectionTestClass method = public final native void java.lang.Object.notify()
reflectionTestClass method = public final native void java.lang.Object.notifyAll()

Process finished with exit code 0

동작 예시

이를테면, 자바에서 알 수 없는 타입의 오브젝트를 갖고 있고 만일 해당 오브젝트에 .doSomething()이라는 메소드가 있다면 수행하는 동작을 만들고 싶은데, 해당 오브젝트가 알려진 인터페이스를 따르지 않는다면 자바의 정적 타이핑 시스템은 이러한 동작을 지원할 수 없다. 리플렉션을 이용하면, 코드에서 해당 오브젝트에 .doSomething() 메소드가 있는지 알아내고 만일 있다면 실행하는 동작이 가능하다.

Method method = foo.getClass().getMethod("doSomething", null);
method.invoke(foo, null);

애노테이션과 함께 쓰이는 유명한 실제 용례 중 하나는 JUnit4에서 @Test 애노테이션이 있는 클래스와 메소드들을 찾아볼 때, Reflection 기술을 사용 후 유닛 테스트가 동작할 때 @Test 애노테이션이 붙은 것을 호출한다.

사실 위의 용례에서 메소드가 있는지 검사하고 있으면 수행하는 방식은 자주 쓰이는 방식은 아니다. 하지만 애노테이션이 붙었는지 확인 후에 실행하거나 하는 방식은 여전히 많이 쓰인다.

이러한 개념은 다른 정적 타입 언어(C#)에 있는 Reflection 과도 매우 비슷하다.

오브젝트 타입을 확인하는 것은 정확히 Reflection의 역할은 아니고, Type Introspection이라고 한다. ReflectionIntrospection을 이용하여 런타임 중에 동작을 변화시키는 것을 말한다. C++와 같은 언어는 Reflection은 지원하지 않고 Introspection만 지원하기 때문에 이 같은 구분을 명확히 해두면 좋다.

Reflection 공식 문서

장단점

장점

  • 확장성 기능 (Extensibility Features): 오브젝트의 완전한 이름을 사용하여 확장성 오브젝트들의 인스턴스를 만들어냄으로써 애플리케이션이 외부, 사용자 정의 클래스들을 사용할 수 있게 된다.
  • 디버깅과 테스트 도구 (Debugging and testing tools): 클래스의 private 멤버를 검사하기 위해 디버거가 리플렉션의 프로퍼티를 사용할 수 있다.

단점

  • 성능 오버헤드: 리플렉션 연산은 당연히 리플렉션이 없는 것보다 느리다. 그래서 자주 호출되는 성능에 민감한 코드에는 적용하지 말아야 한다.
  • 내부의 노출: 리플렉션 코드는 추상화를 파괴하고 플랫폼의 업그레이드 시 동작이 변경될 수도 있다.

레퍼런스

https://www.geeksforgeeks.org/reflection-in-java/
https://stackoverflow.com/questions/37628/what-is-reflection-and-why-is-it-useful

profile
풀스택 웹개발자로 일하고 있는 Jake Seo입니다. 주로 Jake Seo라는 닉네임을 많이 씁니다. 프론트엔드: Javascript, React 백엔드: Spring Framework에 관심이 있습니다.

0개의 댓글