ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Java - Reflection
    Language/Java 2022. 4. 5. 17:33

    Reflection

    Reflection이란?

    리플렉션은 Heap 영역에 로드된 Class 타입의 객체를 통해, 원하는 클래스의 인스턴스를 생성할 수 있도록 지원하고, 인스턴스의 필드와 메서드를 접근 제어자와 상관없이 사용할 수 있도록 지원하는 API이다.

    여기서 로드된 클래스라고 하면, JVM의 Class Loader에서 클래스 파일에 대한 로딩을 완료한 후, 해당 클래스의 정보를 담은 Class 타입의 객체를 생성하여 메모리의 Heap 영역에 저장해 둔 것을 의미한다. new 키워드를 통해 만드는 객체와는 다른 것임을 유의해야 한다.

     

    Reflection 사용 방법

    리플렉션을 사용하기에 앞서, Heap 영역에 로드된 클래스 타입의 객체를 가져와야 한다. 클래스 타입의 객체를 가져오기 위한 방법에는 크게 3가지 방법이 있다.

     

    1. 클래스명.class로 가져오는 방법

    2. 인스턴스명.getClass( )로 가져오는 방법

    3. Class.forName("클래스명")으로 가져오는 방법

     

    public class Main {
        public static void main(String[] args) throws ClassNotFoundException {
            Class<Member> memberClass01 = Member.class;
            System.out.println(System.identityHashCode(memberClass01));
    
            Member member = new Member("홍길동", 23, "게임");
            Class<? extends Member> memberClass02 = member.getClass();
            System.out.println(System.identityHashCode(memberClass02));
    
            Class<?> memberClass03 = Class.forName("com.company.Member");
            System.out.println(System.identityHashCode(memberClass03));
            
            // 출력 결과
            // 460141958
            // 460141958
            // 460141958
        }
    }
    
    class Member {
        private String name;
    
        private int age;
    
        public String hobby;
    
        public Member() {
    
        }
    
        public Member(String name, int age, String hobby) {
            this.name = name;
            this.age = age;
            this.hobby = hobby;
        }
    
        public void speak(String message) {
            System.out.println(message);
        }
    
        private void secret() {
            System.out.println("비밀번호는 1234입니다.");
        }
    
        @Override
        public String toString() {
            return "Member{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    ", hobby='" + hobby + '\'' +
                    '}';
        }
    }

    위 코드와 같이 3가지 방법으로 가져온 Class 타입의 인스턴스가 모두 같은 것을 확인할 수 있다. 어떠한 방식을 사용하든 지 해시 값이 같기 때문에, 상황에 따라 적절하게 사용하면 된다.

    가져온 Class 타입을 통해 해당 클래스의 인스턴스를 생성할 수도 있고, 인스턴스의 필드와 메서드를 접근 제어자와 상관없이 사용할 수 있게 된다.

     

    public class Main {
        public static void main(String[] args) throws Exception {
    
        /** Member 클래스의 모든 생성자 출력 */
        Member member01 = new Member();
        Class<? extends Member> memberClass = member01.getClass();
        Arrays.stream(memberClass.getConstructors()).forEach(System.out::println);
    
        /** Member 클래스의 기본 생성자를 통한 인스턴스 생성 */
        Constructor<? extends Member> defaultConstructor = memberClass.getConstructor();
        Member member02 = defaultConstructor.newInstance();
        System.out.println("member02 : " + member02);
    
        /** Member 클래스의 다른 생성자를 통한 인스턴스 생성 */
        Constructor<? extends Member> fullConstructor = memberClass.getConstructor(String.class, int.class, String.class);
        Member member03 = fullConstructor.newInstance("이순신", 21, "게임");
        System.out.println("member03 : " + member03);
            
        // 출력 결과
        // public com.company.Member()
        // public com.company.Member(java.lang.String,int,java.lang.String)
        // member02 : Member{name='null', age=0, hobby='null'}
        // member03 : Member{name='이순신', age=21, hobby='게임'}
        }
    }

    우선, 로드된 Class 타입의 객체를 통해 인스턴스를 생성해보도록 하겠다. getConstructor( )를 통해 생성자를 얻어 오고, newInstance( )를 통해 Member 인스턴스를 동적으로 생성할 수 있다.

     

    public static void main(String[] args) throws Exception {
    
        Member member = new Member("이순신", 21, "게임");
        Class<? extends Member> memberClass = member.getClass();
    
        /** 필드 접근 */
        Field[] fields = memberClass.getDeclaredFields();
        for (Field field : fields) {
            field.setAccessible(true);
            System.out.println(field.get(member));
        }
        fields[0].set(member, "홍길동");
        System.out.println(member);
    
        /** 메서드 접근 */
        Method speakMethod = memberClass.getDeclaredMethod("speak", String.class);
        speakMethod.invoke(member, "리플렉션 테스트");
    
        Method secretMethod = memberClass.getDeclaredMethod("secret");
        secretMethod.setAccessible(true);
        secretMethod.invoke(member);
    }

    마지막으로, 인스턴스의 필드와 메서드를 접근 제어자와 상관없이 접근해서 사용해보도록 하겠다.

    getDeclaredFields( )를 통해 클래스의 인스턴스 변수를 모두 가져온 후, get( )을 통해 필드 값을 반환받을 수 있고, set( )을 통해 필드 값을 수정할 수 있다. 이때 주의할 점은 private 접근 제어자가 있는 필드에 접근하는 경우에는 setAccessible( )의 인자로 true를 넘겨주어야 한다.

    메서드도 getDeclareMethod( )를 통해 가져올 수 있다. 이때 메서드의 이름과 파라미터의 타입을 같이 인자로 넘겨주어야 한다. 변수의 경우와 마찬가지로 private 접근 제어자가 있는 메서드에 접근할 때에는 setAccessible( )의 인자를 true로 설정해주어야 한다. 또한, invoke( ) 메서드를 통해 리플렉션 API로 얻어 온 메서드를 호출할 수 있다.

     

    Reflection의 장단점

    장점

    런타임 시점에서 클래스의 인스턴스를 생성하고, 접근 제어자와 관계없이 필드와 메서드에 접근하여 필요한 작업을 수행할 수 있는 유연성을 가지고 있다.

     

    단점

    ㆍ 캡슐화를 저해한다.

    ㆍ 런타임 시점에서 인스턴스를 생성하기 때문에, 컴파일 시점에서 해당 타입을 확인할 수 없다.

    ㆍ 런타임 시점에서 인스턴스를 생성하기 때문에, 구체적인 동작 흐름을 파악하기 어렵다.

    ㆍ 단순히 필드 및 메서드를 접근할 때보다 리플렉션을 사용하여 접근할 때 성능이 느리다. (모든 상황에서 성능이 느린 것은 아님)

     

    Reflection의 사용 이유

    리플렉션 API를 통해 런타임 중 클래스 정보에 접근하여 클래스를 원하는 대로 조작할 수 있다. 심지어 private 접근 제어자로 선언한 필드나 메서드까지 조작이 가능하다. 객체 지향 설계에서 중요한 캡슐화가 깨지기 때문에, 사용해서는 안 될 거처럼 보인다.

    규모가 작은 콘솔 단계에서는 개발자가 충분히 컴파일 시점에 프로그램에서 사용될 객체와 의존 관계를 모두 파악할 수 있다. 하지만, 프레임워크와 같이 큰 규모의 개발 단계에서는 수많은 객체와 의존 관계를 파악하는 것은 어려운 일이다. 이때 리플렉션은 사용하면 동적으로 클래스를 만들어서 의존 관계를 맺을 수 있다.

    예를 들어, Spring의 Bean Factory를 보면 @Controller, @Service, @Repository 등의 애너테이션만 붙이면 Bean Factory에서 자동으로 해당 애너테이션이 붙은 클래스를 생성하고 관리해 주는 것을 알 수 있다. 개발자는 Bean Factory에게 클래스에 대한 정보를 알려준 적이 없는데, 이것이 가능한 이유는 바로 리플렉션 덕분이다. 런타임에 해당 애너테이션이 붙은 클래스를 탐색하고 발견한다면, 리플렉션을 통해 해당 클래스의 인스턴스를 생성하고 필요한 피드를 주입하여 Bean Factory에 저장하는 방식으로 사용이 된다.

    물론, 위에서 말했듯이 캡슐화를 저해하기 때문에, 꼭 필요한 상황에서만 사용하는 것이 좋다.


    출처

    https://steady-coding.tistory.com/609

     

    728x90

    'Language > Java' 카테고리의 다른 글

    Java - Wrapper Class  (0) 2022.09.20
    Java - Mutex & Semaphore & Monitor  (0) 2022.04.06
    Java - hashCode() & equals()  (0) 2022.04.05
    Java - Thread  (0) 2022.03.28
    Java - Overloading & Overriding  (0) 2022.03.23

    댓글

Designed by Tistory.