JAVA

[Java] 자바 리플렉션(Reflection) 실습하기

Stark97 2023. 11. 18. 20:17
반응형
 
 

자바 코드를 직접 적어보며 자바 리플렉션의 사용 방법을 알아보자

혹시 자바 리플렉션에 대해서 잘 알지 못한다면 아래의 포스트를 보고 오자!

 

[Java] 자바 리플렉션(reflection)이란?

이번 포스트에서는 자바 리플렉션과 이걸 사용하는 스프링에 대해서 알아보자 1. 자바 리플렉션이란 리플렉션이란? 리플렉션은 자바에서 클래스나 멤버에 대한 정보를 런타임에 조사하고, 조작

curiousjinan.tistory.com

 

1. 리플렉션으로 private 필드와 메서드에 접근하기

클래스와 생성자 정의

  • 이 코드에서 FirstReflectionExample 클래스는 secret과 number라는 두 개의 private final 필드를 가지고 있다. 생성자는 이 두 필드에 값을 할당하는 역할을 한다.

클래스와 생성자 정의하기
클래스와 생성자 정의하기

비공개 메서드 정의

  • secretMethod는 private로 선언되어 클래스 외부에서는 접근할 수 없다. 이 메서드는 secret과 number 필드 값을 출력한다.

비공개 메서드 정의하기
비공개 메서드 정의하기

리플렉션을 사용한 접근

  • import는 java.lang.reflect의 Field, Method를 해준다.

reflect의 메서드를 의존한다.
reflect의 메서드를 의존한다.

  • useReflection 메서드는 FirstReflectionExample의 인스턴스를 생성하고, 리플렉션을 사용하여 secret 필드의 값을 가져오고 secretMethod 메서드를 호출한다. Field와 Method 클래스를 사용하여 비공개 멤버에 접근한다.

리플랙션을 사용하여 필드, 메서드에 접근
리플랙션을 사용하여 필드, 메서드에 접근

메인 메서드와 실행

  • main() 메서드는 프로그램의 진입점이다. 이 메서드에서는 FirstReflectionExample의 인스턴스를 생성하고 useReflection 메서드를 호출하여 리플렉션의 실행을 확인한다.

메인 메서드와 실행하는 로직
메인 메서드와 실행하는 로직

main함수 실행 결과

  • 실행 결과 아래와 같은 결과값을 받아 볼 수 있었다.

main함수 실행 결과
main함수 실행 결과

실습을 위한 전체 코드

package com.example.javareflectionstudy.pojo;

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

public class FirstReflectionExample {

    private final String secret;
    private final int number;

    public FirstReflectionExample(String secret, int number) {
        this.secret = secret;
        this.number = number;
    }

    private void secretMethod() {
        System.out.println("Secret: " + secret + ", Number: " + number);
    }

    // 리플렉션을 사용하여 비공개 필드와 메서드에 접근하는 메서드
    public void useReflection() throws Exception {
        FirstReflectionExample myObject = new FirstReflectionExample("hidden", 123);
        Class<?> clazz = myObject.getClass();

        // 비공개 필드 'secret'에 접근
        Field secretField = clazz.getDeclaredField("secret");
        secretField.setAccessible(true);
        System.out.println("secret 필드의 값: " + secretField.get(myObject));

        // 비공개 메서드 'secretMethod' 호출
        Method secretMethod = clazz.getDeclaredMethod("secretMethod");
        secretMethod.setAccessible(true);
        System.out.println("secretMethod 메서드 실행:");
        secretMethod.invoke(myObject);
    }

    public static void main(String[] args) {
        try {
            // FirstReflectionExample 인스턴스 생성
            FirstReflectionExample example = new FirstReflectionExample("test", 42);

            // useReflection 메서드를 호출하여 리플렉션 사용 확인
            example.useReflection();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

 

2. 실습: 리플렉션으로 Dog 클래스 정보 조회

Dog 클래스 정의

  • SecondReflectionExample의 내부에 static 클래스로 선언된 Dog 클래스는 리플렉션을 실습하기 위한 간단한 예제 클래스다. 여기서는 Dog 클래스가 SecondReflectionExample 클래스의 내부 클래스로 선언되었기 때문에, static으로 선언해 주었다. 이렇게 하면 Dog 클래스의 인스턴스를 SecondReflectionExample 클래스의 인스턴스 없이도 생성할 수 있다.
만약 내부 클래스가 외부 클래스의 인스턴스 필드나 메서드에 접근할 필요가 없다면 static으로 선언하는 것이 좋다. 이는 클래스가 더 독립적이고 재사용 가능하게 만들며, 메모리 사용 측면에서도 효율적이다. 반면, 내부 클래스가 외부 클래스의 인스턴스 멤버에 접근해야 하는 경우에는 static을 사용하지 않는 것이 적절하다.

실습을 위해 Dog 클래스를 정의한다.
실습을 위해 Dog 클래스를 정의한다.

메인 메서드와 리플렉션 사용

  • main() 메서드에서는 Dog 클래스의 인스턴스를 생성하고 리플렉션을 통해 클래스의 정보를 조회한다. 클래스 이름, 슈퍼클래스, 접근 제한자, 메서드, 필드, 생성자 정보를 출력한다.

메인 메서드에서 리플렉션 사용하기
메인 메서드에서 리플렉션 사용하기

  • 위의 코드 내부 주석에 작성할 메서드는 reflect 패키지를 import하여 리플렉션 코드를 작성하면 된다.

import reflect

클래스 이름, 슈퍼 클래스, 접근 제한자 정보 출력

  • getName(), getSuperclass().getName(), Modifier.toString(getModifiers()) 메서드를 사용하여 클래스 이름, 슈퍼클래스, 접근 제한자 정보를 출력한다.

리플렉션으로 클래스 정보 및 접근 제한자 정보 받기
클래스 정보 및 접근 제한자 정보 받기

메서드(Method) 정보 가져오기

  • getDeclaredMethods() 메서드를 통해 클래스 내의 모든 메서드를 조회하고, 각 메서드의 이름, 접근 제한자, 반환 타입을 출력한다.

리플렉션으로 메서드 정보 가져오기
메서드 정보 가져오기

필드(Field) 정보 가져오기

  • getField("type") 메서드로 type 필드에 접근하고, 해당 필드의 값을 설정 및 조회한다.

리플렉션으로 필드 정보 가져오기
필드 정보 가져오기

생성자 정보 가져오기

  • getDeclaredConstructors() 메서드를 사용하여 클래스의 모든 생성자 정보를 조회하고, 각 생성자의 이름, 접근 제한자, 파라미터 수를 출력한다.

리플렉션으로 생성자 정보 가져오기
생성자 정보 가져오기

메인함수 실행 결과

메인함수 실행 결과 프린트창
메인함수 실행 결과 프린트창

실습을 위한 전체 코드

package com.example.javareflectionstudy.pojo;

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

    public class SecondReflectionExample {

    /**
     * Dog 클래스가 SecondReflectionExample 클래스의 내부 클래스로 선언되었기 때문에, static으로 선언하는 것이 맞다.
     * 이렇게 하면 Dog 클래스의 인스턴스를 SecondReflectionExample 클래스의 인스턴스 없이도 생성할 수 있다.
     */
    static class Dog {
        public String type; // 필드 추가

        public void display() {
            System.out.println("나는 reflection을 검증하는 강아지다.");
        }
    }

    public static void main(String[] args) throws IllegalAccessException, NoSuchFieldException {
        Dog dog = new Dog();
        Class<? extends Dog> obj = dog.getClass();

        /**
         * 클래스 이름, 슈퍼 클래스, 접근 제한자 정보 출력하기
         *
         * 생성한 객체의 클래스 정보를 가져오기 위해 getClass() 메서드를 사용할 수 있다.
         * 이를 통해 클래스 이름, 수퍼클래스, 접근 제한자 등의 정보를 얻을 수 있다.
         */
        System.out.println();
        System.out.println("====================== 클래스 이름, 슈퍼 클래스, 접근 제한자 정보 출력 ======================");
        System.out.println("Name: " + obj.getName());
        System.out.println("Superclass: " + obj.getSuperclass().getName());
        System.out.println("Modifier: " + Modifier.toString(obj.getModifiers()));
        System.out.println("======================================= 종료 =======================================");
        System.out.println();

        /**
         * Method 정보 가져오기
         *
         * Method 클래스를 사용하여 클래스 내의 메서드 정보를 가져올 수 있다.
         * 이를 통해 메서드 이름, 접근 제한자, 반환 타입 등을 확인할 수 있어
         */
        Method[] methods = obj.getDeclaredMethods();
        for (Method m : methods) {
            System.out.println("====================== Method(메서드) 정보 가져오기 ======================");
            System.out.println("Method Name: " + m.getName());
            System.out.println("Modifier: " + Modifier.toString(m.getModifiers()));
            System.out.println("Return Types: " + m.getReturnType());
            System.out.println("================================ 종료 ================================");
            System.out.println();
        }

        /**
         * 필드 정보 가져오기
         *
         * Field 클래스를 이용해 필드 정보를 가져오고 수정할 수 있다.
         * 필드 값 설정 및 가져오기, 접근 제한자 확인이 가능하다.
         */
        Field field1 = obj.getField("type");
        field1.set(dog, "labrador");
        String typeValue = (String) field1.get(dog);
        System.out.println("====================== 필드(Field) 정보 가져오기 ======================");
        System.out.println("Value: " + typeValue);
        System.out.println("Modifier: " + Modifier.toString(field1.getModifiers()));
        System.out.println("=============================== 종료 ===============================");
        System.out.println();

        /**
         * 생성자 정보 가져오기
         *
         * Constructor 클래스를 사용해 클래스의 생성자 정보를 가져올 수 있다.
         * 생성자 이름, 접근 제한자, 파라미터 수 등을 확인할 수 있다.
         */
        Constructor[] constructors = obj.getDeclaredConstructors();
        for (Constructor c : constructors) {
            System.out.println("============================== 생성자(Constructor) 정보 가져오기 ==============================");
            System.out.println("Constructor Name: " + c.getName());
            System.out.println("Modifier: " + Modifier.toString(c.getModifiers()));
            System.out.println("Parameters: " + c.getParameterCount());
            System.out.println("========================================== 종료 ===========================================");
            System.out.println();
        }
    }

}

 

3. 실습: 리플렉션으로 JSON 직렬화 구현

이 예제는 리플렉션을 활용하여 객체의 필드를 동적으로 접근하고, 이를 기반으로 JSON 문자열을 생성하는 방법을 보여준다. 이러한 방식은 다양한 객체에 대해 유연한 JSON 직렬화를 가능하게 한다.

 

Record 클래스 정의

  • 자바의 Record 타입을 사용하여 SimplePOJO 클래스를 정의한다. (Record는 데이터를 보관하는 불변 객체를 간결하게 생성할 수 있는 기능을 제공한다.)

자바 Record 정의
자바 Record 정의

JSON 직렬화 함수 구현

  • 리플렉션을 사용하여 객체의 필드를 순회하고, 이를 JSON 형식의 문자열로 변환하는 함수를 구현한다. 이 함수는 클래스의 모든 필드를 순회하면서 필드의 이름과 값을 가져와 JSON 형식으로 조합한다.

JSON 직렬화 함수 구현하는 코드
JSON 직렬화 함수 구현하는 코드

직렬화 함수 테스트

  • SimplePOJO 클래스의 인스턴스를 생성하고, 이를 convertToJson 함수에 전달하여 JSON 문자열을 얻는다. 이를 통해 리플렉션을 활용한 JSON 직렬화의 결과를 확인할 수 있다.

직렬화 함수 테스트를 하는 메인 메서드
직렬화 함수 테스트를 하는 메인 메서드

메인함수 실행 결과

메인함수 실행 결과 프린트창
메인함수 실행 결과 프린트창

실습을 위한 전체 코드

package com.example.javareflectionstudy.pojo;

import java.lang.reflect.Field;

public class JsonSerializationExample {

    record SimplePOJO(
            String name,
            int age,
            boolean isStudent
    ) {
    }

    public static String convertToJson(Object obj) throws IllegalAccessException {
        Class<?> clazz = obj.getClass();  // 객체의 클래스 정보를 가져옴
        StringBuilder jsonBuilder = new StringBuilder("{");  // JSON 문자열을 구성하기 위한 StringBuilder
        Field[] fields = clazz.getDeclaredFields();  // 클래스의 모든 필드를 가져옴

        for (int i = 0; i < fields.length; i++) {
            Field field = fields[i];
            field.setAccessible(true);  // private 필드에도 접근할 수 있도록 설정

            // JSON 키:값 쌍을 구성
            jsonBuilder.append("\"").append(field.getName()).append("\":");

            // 필드 타입이 String일 경우 따옴표로 감싸고, 그렇지 않으면 그대로 값을 추가
            if (field.getType().equals(String.class)) {
                jsonBuilder.append("\"").append(field.get(obj)).append("\"");
            } else {
                jsonBuilder.append(field.get(obj));
            }

            // 마지막 필드가 아니면 쉼표 추가
            if (i < fields.length - 1) {
                jsonBuilder.append(",");
            }
        }

        jsonBuilder.append("}");  // JSON 문자열 닫기
        return jsonBuilder.toString();  // 완성된 JSON 문자열 반환
    }

    public static void main(String[] args) throws IllegalAccessException {
        SimplePOJO pojo = new SimplePOJO("John Doe", 25, true);
        String jsonResult = convertToJson(pojo);
        System.out.println();
        System.out.println("======================================================================================");
        System.out.println("reflection을 활용하여 json으로 변환시킨 결과값: " + jsonResult);
        System.out.println("======================================================================================");
    }
}

 

📌  마무리

자바 리플렉션을 사용하는 코드를 직접 작성해 보면서 공부했다. 이론적으로만 알고 있기보단 역시 실제로 사용해 보는 게 기억에도 잘 남는 것 같으니 혹시나 시간이 있으시다면 한 번쯤 전체 코드를 따라서 쳐보는 것도 좋을 것이라고 생각한다.

 

반응형