개발자는 기록이 답이다

🐙 Reflection이란 무엇이고 어떻게 사용하는것인가? 본문

언어/Java

🐙 Reflection이란 무엇이고 어떻게 사용하는것인가?

slow-walker 2023. 12. 23. 23:02

리플렉션

  • 클래스로더를 통해 읽어온 클래스 정보(거울에 "반사"된 정보)를 사용하는 기술
  • 리플렉션을 이용해 클래스를 읽어오거나 인스턴스를 만들거나, 메소드를 실행하거나, 필드의 값을 가져오거나 변경하는 것이 가능

자바는 클래스와 인터페이스의 메타 정보를 Class 객체로 관리한다.

메타 정보 : 패키지 정보, 타입 정보, 멤버(생성자, 필드, 메소드) 정보 등

이러한 메타 정보를 프로그램에서 읽고 수정하는 행위를 리플랙션이라고 한다.

 

모든 바이트 코드는 클래스라는 객체로 관리된다. 클래스와 인터페이스를 사용하려면 바이트 코드를 메소드 영역에 저장해야 한다.

클래스별로 저장되어 있는 정보를 가지고 클래스라는 타입으로 객체가 만들어진다. 

리플랙션을 사용하려면 이러한 클래스 객체(메타정보)부터 얻어야 한다.

 

리플랙션을 사용하기 위해 클래스 객체 얻기 

 

메소드 영역에 저장된 바이트 코드의 정보를 가진 클래스 객체를 얻으려면 3가지 방법이 있다.

 

1. Class clazz = 클래스 이름.class;

2. Class clazz = Class.forName("패키지...클래스이름");

3. Class clazz = 객체참조변수.getClass();

1. Class clazz = String.class;

2. Class clazz = Class.forName("java.lang.String");

3. String str = "자바에 진심"
Class clazz = str.getClass();

 

※ 클래스 객체를 얻어서 newInstance()로 인스턴스 객체를 얻을 수 있었지만, deprecated되서 생성자를 통해 얻어야 한다.

패키지와 타입 정보 얻기

 

얻은 클래스 객체를 통해 리플랙션을 한다.

메소드 용도
Package getPackage() 패키지 정보 읽기
String getSimpleName() 패키지를 제외한 타입 이름
String getName() 패키지를 포하한 전체 타입 이름

 

package com.example.fctdd;

public class Car {
}
package com.example.fctdd;

public class GetClassExample {
    public static void main(String[] args) {
        // 방법1
        Class clazz = Car.class;

        // 방법2
//        Class clazz = Class.forName("com.example.fctdd.Car");

        // 방법 3
//        Car car = new Car();
//        Class clazz = car.getClass();

        System.out.println("패키지: " + clazz.getPackage());
        System.out.println("패키지 이름: " + clazz.getPackage().getName());
        System.out.println("클래스 간단 이름: " + clazz.getSimpleName());
        System.out.println("클래스 전체 이름: " + clazz.getName());
    }package com.example.fctdd;

    public class GetClassExample {
        public static void main(String[] args) {
            // 방법1
            Class clazz = Car.class;

            // 방법2
//        Class clazz = Class.forName("com.example.fctdd.Car");

            // 방법 3
//        Car car = new Car();
//        Class clazz = car.getClass();

            System.out.println("패키지: " + clazz.getPackage());
            System.out.println("패키지 이름: " + clazz.getPackage().getName());
            System.out.println("클래스 간단 이름: " + clazz.getSimpleName());
            System.out.println("클래스 전체 이름: " + clazz.getName());
        }
    }
}

 

 

멤버 정보 얻기

 

타입(클래스, 인터페이스)가 가지고 있는 멤버 정보(필드,메소드,생성자)를 얻기

 

메소드 용도
Constructor[] getDeclaredConstructors() 생성자 정보 읽기
Field[] getDeclaredFields() 필드 정보 읽기
Method[] getDeclaredMethods() 메소드 정보 읽기

 

※ 왜 배열일까? 한 클래스의 생성자, 필드, 메소드는 오버로딩으로 여러개가 될 수 있기 때문에 배열이다.

 

package com.example.fctdd.getMember;

public class Car {
    //필드
    private String model;
    private String owner;

    //생성자
    public Car() {
    }

    public Car(String model) {
        this.model = model;
    }

    //메소드

    public String getModel() {
        return model;
    }

    public void setModel(String model) {
        this.model = model;
    }

    public String getOwner() {
        return owner;
    }

    public void setOwner(String owner) {
        this.owner = owner;
    }
}
package com.example.fctdd.getMember;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class ReflectionExample {
    public static void main(String[] args) {
        Class clazz = Car.class;
        System.out.println("[생성자 정보]");
        Constructor[] constructors = clazz.getDeclaredConstructors();
        for (Constructor constructor : constructors) {
            System.out.print(constructor.getName() + "(");
            Class[] parameters = constructor.getParameterTypes();
            printParameters(parameters);
            System.out.println(")");
        }
        System.out.println();

        System.out.println("[필드 정보]");
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            System.out.println(field.getType().getName() + " " + field.getName());
        }
        System.out.println();

        System.out.println("[메소드 정보]");
        Method[] methods = clazz.getDeclaredMethods();
        for (Method method : methods    ) {
            System.out.print(method.getName() + "(");
            Class[] parameters = method.getParameterTypes();
            printParameters(parameters);
            System.out.println(")");
        }
    }
    //생성자 및 메소드의 매개변수 정보 출력
    private static void  printParameters(Class[] parameters) {
        for (int i = 0; i < parameters.length; i++) {
            System.out.print(parameters[i].getName());
            if (i < (parameters.length-1)) {
                System.out.print(",");
            }
        }
    }

}

 

리소스 경로 얻기

 

클래스가 존재하는 곳. 즉, 컴파일해서 바이트 코드가 존재하는 곳은 바이트 코드만 있는게 아니라 resource파일도 있을 수 있다.

resource는 데이터를 갖고 있는 파일을 얘기하는데, 대표적으로 이미지 파일, 프로퍼티 파일을 말한다.

 

Class객체는 클래스 파일(~.class)의 경로 정보를 가지고 있기 때문에, 이 경로를 기준으로 상대 경로에 있는 다른 리소스 파일(이미지, XML, Property 파일)의 정보를 얻을 수 있다. 즉, 클래스 바이트 코드 파일을 기준으로 해서 리소스 경로 정보를 얻는다.

메소드 용도
URL getResource(String name) 리소스 파일의 URL리턴
InputStream getResourceAsStream(String name) 리소스 파일의 InputStream리턴

 

  • getResoruce() : 경로 정보가 담긴 URL 객체 리턴
  • getResoureAsStream() : 파일의 내용을 읽을 수 있도록 InputStream 객체 리턴

 

 

그렇다면 이런 코드를 언제 사용하는 걸까? Java도 Swing이나 JavaFX 라이브러리를 사용하면 윈도우 프로그램을 만들 수 있다.

프로그램에 들어가는 고정적인 이미지들이 있는 경우(버튼위에 이미지, 백그라운드 이미지) 이런 바이트 코드랑 같이 저장한다.

이처럼 파일안에 있는 데이터를 읽어들일때 전체 경로가 필요할 경우에 위의 메소드를 사용하면 된다.


 

🤔 느낀점

 

리플랙션을 위해 사용하는 메소드들이 많아서 여러 블로그들을 둘러봤을때는 너무 혼란스러웠는데,

"이것이 자바다" 기본적인게 잘 정리되어있어서 좋았다.

자바 관련 책 하나를 다 읽었다고 블로그만 뒤적이는것 보다,

이렇게 필요한 내용이 잘 정리되어 있는 책이랑 강의가 있는지 찾아보는것도 역량인 것 같다.

 

백기선님 인프런 강의 : 이택지브 자바 완벽 공략 1부 Item01

신용권님 강의 : 이것이 자바다 - 리플렉션

The Java Tutorials