Reflection

김연수·2023년 2월 24일
0

Java

목록 보기
3/7

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

사용 방법

  • 클래스.class 로 가져오기
  • 인스턴스.getClass() 로 가져오기
  • Class.forName("클래스명") 으로 가져오기

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

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

예시

public class Member {

    private String name;

    protected 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 + '\'' +
            '}';
    }
}

public class Main {

    public static void main(String[] args) throws ClassNotFoundException {
        Class<Member> memberClass = Member.class;
        System.out.println(System.identityHashCode(memberClass));

        Member member = new Member("제이온", 23, "다라쓰 개발");
        Class<? extends Member> memberClass2 = member.getClass();
        System.out.println(System.identityHashCode(memberClass2));

        Class<?> memberClass3 = Class.forName("{패키지명}.Member");
        System.out.println(System.identityHashCode(memberClass3));
    }
}

// 실행 결과
1740000325
1740000325
1740000325

해당 클래스의 인스턴스를 생성

public class Main {

    public static void main(String[] args) throws Exception {
        // Member의 모든 생성자 출력
        Member member = new Member();
        Class<? extends Member> memberClass = member.getClass();
        Arrays.stream(memberClass.getConstructors()).forEach(System.out::println);

        // Member의 기본 생성자를 통한 인스턴스 생성
        Constructor<? extends Member> constructor = memberClass.getConstructor();
        Member member2 = constructor.newInstance();
        System.out.println("member2 = " + member2);

        // Member의 다른 생성자를 통한 인스턴스 생성
        Constructor<? extends Member> fullConstructor =
            memberClass.getConstructor(String.class, int.class, String.class);
        Member member3 = fullConstructor.newInstance("제이온", 23, "다라쓰 개발");
        System.out.println("member3 = " + member3);
    }
}

// 실행 결과
public Member()
public Member(java.lang.String,int,java.lang.String)
member2 = Member{name='null', age=0, hobby='null'}
member3 = Member{name='제이온', age=23, hobby='다라쓰 개발'}

getConstructor() 를 통해 생성자를 얻어 오고, new Instance()를 통해 Member 인스턴스를 동적으로 생성 해 줄수 있다.

인스턴스의 필드와 메소드를 접근 제어자와 상관 없이 접근하여 사용

public class Main {

    public static void main(String[] args) throws Exception {
        Member member = new Member("제이온", 23, "다라쓰 개발");
        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, "제이온2");
        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);
    }
}

// 실행 결과
제이온
23
다라쓰 개발
Member{name='제이온2', age=23, hobby='다라쓰 개발'}
리플렉션 테스트
비밀번호는 1234입니다.
  • getDeclaredFileds() 를 통해 클래스의 인스턴스 변수를 모두 가져올 수 있고, get() 을 통해 필드 값을 반환 받을 수 있다.
  • set() 을 통해 필드 값을 수정 할 수 있다.
    *이때 주의할 점은 private 접근 제어자가 있는 필드에 접근할 때는 setAccessible()의 인자를 true로 넘겨 주어야 한다.
  • 메소드도 getDeclaredMethod() 를 통해 가져올 수 있다. 이때 메소드의 이름과 파라미터의 타입을 같이 인자로 넘겨줘야 한다.
    *private 접근 제어자가 있는 메소드에 접근 할 때는 setAccessible() 의 인자를 true로 설정해야 한다.
  • 마지막으로 invoke() 메소드를 통해 리플렉션 API로 얻어 온 메소드를 호출한다.

장단점

장점

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

단점

  • 캡슐화를 저해한다
  • 런타임 시점에서 인스턴스를 생성하므로 컴파일 시점에서 해당 타입을 체크 할 수 없다
  • 런타임 시점에서 인스턴스를 생성하므로 구체적인 동작 흐름을 파악하기 어렵다
  • 단순히 필드 및 메소드를 접근할 때보다 리플렉션을 사용 하여 접근할 때 성능이 느리다

추가+ Reflection 사용 시 주의사항

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

  • 보안 제안 사항 : Reflection에는 시큐리티 매니저의 실행 시에 존재하지 않는 실행 시 액세스 권한이 필요하다. 이것은 애플릿과 같이 제한된 보안 컨텍스트에서 실행되어야 하는 코드에 대한 중요한 고려 사항이다

사용하는 이유

리플렉션 API를 통해 런타임 중, 클래스 정보에 접근하여 클래스를 원하는대로 조작 할 수 있다.

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

Spring의 Bean Factory를 보면, @Controller @Service 등 어노테이션만 붙이면 알아서 해당 어노테이션이 붙은 클래스를 생성하고 관리해주는 것을 알 수 있는데. 이것이 가능한 이유는 바로 리플렉션이다. 런타임에 어노테이션이 붙은 클래스를 탐색하고 발견한다면, 리플렉션을 통해 해당 클래스의 인스턴스를 생성하고 필요한 필드를 주입하여 Bean Factory에 저장하는 식으로 사용된다

profile
코린이

0개의 댓글