[java] 리플렉션(Reflection) 1 : 리플렉션 기본

반응형

리플렉션 (Reflection)

 

리플렉션은 class, constructor, field, method 등을 다루기 위한 java표준 API이다. 클래스나 메소드의 메타정보를 동적으로 획득하고, 코드도 동적으로 호출할 수 있다.


다음과 같은 클래스가 있다.

패키지 클래스
java.lang Class
java.lang.reflect Constructor
java.lang.reflect Field
java.lang.reflect Method

 

Spring 등의 프레임워크는 많은 내부처리에서 리플렉션을 이용하고 있다. 따라서 리플렉션을 이해하면 다음과 같은 장점이 있다.
・프레임워크의 소스 코드를 이해할 수 있다.
・소스 코드를 읽지않고도 프레임워크 내부의 처리를 이미지화 할 수 있다.
・스스로 프레임워크를 만들수 있다.

다만, 보통 실무의 업무로직등에서는 리플렉션기술을 사용하는 것을 권하지 않는다. 리플렉션으로 클래스와 메소드의 메타정보를 사용해서 어플리케이션을 동적으로 유연하게 만들 수 있지만, 리플렉션은 런타임에 동작하기 때문에 컴파일 시점에 오류를 잡을 수 없다.

따라서 리플렉션은 일반적으로 사용하는 것을 권장하지 않는다. 컴파일시점에 오류를 잡도록 해야 더 안전하다. 즉, 프레임워크 개발 또는 매우 일반적인 공통 처리가 필요할 때 부분적으로 사용해야 한다.

 

 

리플렉션 기본

1. Class 클래스

Class 는 클래스를 나타내는 클래스이다. 리플렉션의 중심적인 존재이다.

Class 클래스의 정의는 Class<T> 같이 제네릭이 붙어있다. T 는 클래스를 지정한다.

전체 클래스에는 static 을 암묵적으로 작성하고있다. 그 클래스를 지칭하는 Class 인스턴스가 대입되어있다.

public class Member {
    public static final Class<Member> class = (Member 를 지칭하는 Class 인스턴스);
}

 

여기에 더해 모든 클래스에는 getClass() 라는 메소드가 정의되어 있다. (java.lang.Object 클래스에 정의되어 있다.)

이 메소드의 반환값은 .class 와 같은 '그 클래스를 지칭하는 Class 인스턴스'이다.

Member member = new Member();
Class<? extends Member> memberClass = member.getClass();

 

2. 클래스명 취득

Class 클래스에는 클래스명을 취득하기위한 메소드가 있다.

메소드명 설명
getName() FQCN (Fully Qualified Class Name) 이 반환된다.
getSimpleName() 단순한 클래스명이 반환된다.

 

예제 코드

package hello.proxy.reflection;

import org.junit.jupiter.api.Test;

public class ReflectionTest {

    public class Member {
    }

    @Test
    void nameTest() {
        Class<?> objectClass = Object.class;
        String fqcn = objectClass.getName();
        System.out.println("getName() = " + fqcn);
        String simpleName = objectClass.getSimpleName();
        System.out.println("getSimpleName() = " + simpleName);

        Class<Member> memberClass = Member.class;
        String memberFqcn = memberClass.getName();
        System.out.println("getName() = " + memberFqcn);
        String memberSimpleName = memberClass.getSimpleName();
        System.out.println("getSimpleName() = " + memberSimpleName);
    }

}

출력결과

getName() = java.lang.Object
getSimpleName() = Object
getName() = hello.proxy.reflection.ReflectionTest$Member
getSimpleName() = Member

 

3. 리플렉션으로 인스턴스 생성

예제코드

Team.java

public class Team {

    public String name; // 학습위해 public
    private int number;

    public Team(String name, int number) {

        this.name = name;
        this.number = number;
    }

    public int transferNumber(int value) {
        return number + value;
    }

    @Override
    public String toString() {
        return "Team{" +
                "name='" + name + '\'' +
                ", number=" + number +
                '}';
    }
}

ReflectionTest.java

public class ReflectionTest {

    @Test
    void instanceTest() {
        try {
            // Team 클래스를 지칭하는 Class 인스턴스를 생성
            Class<?> teamClass = Class.forName("hello.proxy.reflection.Team");
            // teamClass 으로 생성자 취득 : getConstructor()
            Constructor<?> constructor = teamClass.getConstructor(String.class, int.class);
            // 생성자로 인스턴스 생성
            Object teamInstance = constructor.newInstance("test name", 2);
            // 인스턴스 생성 확인
            System.out.println(teamInstance);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

출력확인

Team{name='test name', number=2}

name, number 로 설정한 test name, 2 로 생성된 Team 인스턴스를 확인할 수 있다.

 

 

4. 리플렉션으로 필드 사용

Class 클래스에는 필드를 취득하는 메소드가 있다.

메소드명 public 필드 취득 public 이외 필드 취득 슈퍼클래스에 정의된 필드 취득
getField() ✖️
getDeclaredField() ✖️

 

위 예제코드의 Team.java 와 ReflectionTest.java 를 이어서 이용한다.

public class ReflectionTest {

    @Test
    void fieldTest() {
        try {
            // Team 클래스를 지칭하는 Class 인스턴스를 생성
            Class<?> teamClass = Class.forName("hello.proxy.reflection.Team");
            System.out.println("===== getFields() : public Field 만 취득 =====");
            Arrays.stream(teamClass.getFields()).forEach(System.out::println);
            System.out.println("===== getDeclaredFields() : 모든 Field 취득 =====");
            Arrays.stream(teamClass.getDeclaredFields()).forEach(System.out::println);
            System.out.println();

            // teamClass 으로 생성자 취득 : getConstructor()
            Constructor<?> constructor = teamClass.getConstructor(String.class, int.class);
            // 생성자로 인스턴스 생성
            Object teamInstance = constructor.newInstance("test name", 2);

            // public 필드 : number 취득
            Field numberField = teamClass.getDeclaredField("number");
            numberField.set(teamInstance, 999);
            System.out.println("===== public field 값 변경 확인 =====");
            System.out.println("number 변경 -> " + teamInstance);

            // private 필드 : name 취득
            Field nameField = teamClass.getDeclaredField("name");
            // private 값에 set 접근 하면 java.lang.IllegalAccessException 발생
            // setAccessible(true) 를 설정하면 private 필드도 값을 취득 및 설정할 수 있다.
            nameField.setAccessible(true);
            // name 필드 값을 취득
            Object name = nameField.get(teamInstance);
            nameField.set(teamInstance, "이름 변경");

            System.out.println("===== private field 값 변경 확인 =====");
            System.out.println("이름 변경 -> " + teamInstance);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

출력확인

===== getFields() : public Field 만 취득 =====
public int hello.proxy.reflection.Team.number
===== getDeclaredFields() : 모든 Field 취득 =====
private java.lang.String hello.proxy.reflection.Team.name
public int hello.proxy.reflection.Team.number

===== public field 값 변경 확인 =====
number 변경 -> Team{name='test name', number=999}
===== private field 값 변경 확인 =====
이름 변경 -> Team{name='이름 변경', number=999}

 

 

5. 리플렉션으로 메소드 사용

메소드명 public 필드 취득 public 이외 필드 취득 슈퍼클래스에 정의된 필드 취득
getMethod() ✖️
getDeclaredMethod() ✖️

 

위 예제코드의 Team.java 를 이어서 이용한다.

public class ReflectionTest {

    @Test
    void methodTest() {
        try {
            System.out.println("===== 메소드 목록 =====");
            Arrays.stream(Team.class.getDeclaredMethods()).forEach(System.out::println);

            // Team 클래스의 transferNumber 메소드 취득
            Method numberMethod = Team.class.getMethod("transferNumber", int.class);
            // Team 데이터
            Team team = new Team("빌리", 10);
            System.out.println("===== 메소드 실행 전 =====");
            System.out.println(team);
            // transferNumber 메소드 실행 : 10 + 5 = 15 리턴
            Object obj = numberMethod.invoke(team, 5);
            System.out.println("===== invoke() 메소드 실행 후 =====");
            System.out.println("number = " + obj);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

출력확인

===== 메소드 목록 =====
public java.lang.String hello.proxy.reflection.Team.toString()
public int hello.proxy.reflection.Team.transferNumber(int)
===== 메소드 실행 전 =====
Team{name='빌리', number=10}
===== invoke() 메소드 실행 후 =====
number = 15

 

6. 이외에도 가능한 것

  • 상위클래스 취득 : getSuperclass()
  • 생성자 취득: getConstructor(), getConstructors()getDeclaredConstructor()getDeclaredConstructors()
  • 인터페이스 목록 취득 : getInterfaces()

 

 

 

 

반응형