Java

[Java] 리플렉션 (Reflection)에 대하여

DH_0518 2024. 4. 29. 17:28

리플렉션이란, 간단히 말해 컴파일 시점에 타입을 결정하는 정적 언어인 Java에서, 런타임시에 구체적인 Class의 Type을 알지 못하더라도 동적으로 클래스의 메서드, 타입, 변수들에 접근하여 정보를 추출할 수 있도록 해주는 api이다. 예를들어 다음의 상황들에서 리플렉션을 사용하고 있다

 

Reflection의 사용

  • Dynamic Binding: 코드에서 동적으로 Class를 사용할 때 사용
  • Spring Framework: DI, Annotation, Test Code 작성 등에서 사용
  • MVC: View에서 넘어오는 데이터를 객체에 바인딩할 때 사용
  • Hibernate: @Entity 클래스에 setter가 없으면, 해당 필드에 값을 바로 주입
  • IDE: 자동 완성 기능

 

Example

  • 당신은 블로그를 개발하고 있고, 그 블로그는 테마를 정해서 글을 작성해야 한다
  • 현재는 두 가지 테마 'Light Mode'와 'Dark Mode'가 존재해서, 글 작성시 switch문을 사용하여 테마를 정하게 하려고 한다
public static void main(String[] args) {
    // 블로그 생성
    Blog.Owner owner = Blog.Owner.blogOwner("KDH", 28);
    Blog myBlog = Blog.createNewBlog(owner);

    // 테마 선택 후 글 작성
    Blog.Writing writing = Blog.Writing.write(selectTheme("DarkMode"), "Hello, World!");
    myBlog.addWriting(writing);

}

// 테마 선택하여 글 작성
public static BlogTheme selectTheme(String themeName) {
    return switch (themeName) {
        case "LightMode" -> LightMode.writingWithLightMode();
        case "DarkMode" -> DarkMode.writingWithDarkMode();
        default -> throw new IllegalArgumentException("No such theme");
    };
}
  • 그런데 테마 선택의 자유도를 높이기 위해 유저들이 요청한 커스텀 테마를 추가해주려고 한다면 어떻게 해야할까?
  • 'writingWithTheme' 메서드를 보면, 처음 몇 개의 테마만 존재할때는 switch문으로 가볍게 대응할 수 있을 것이다. 하지만 테마의 수가 10개, 50개, 100개가 된다면?
  • if/else나 switch문이 수십, 수백개가 필요 할 것이고, 이는 객체지향의 핵심 원칙인 재사용성과 유지보수성을 저해하는 결과를 가져오게 된다
// Reflection을 사용하여 동적 바인딩
public static BlogTheme selectTheme(String themeName) {
    try {
        Class<?> blogThemeClass = Class.forName("reflection." + themeName);
        Constructor<?> constructor = blogThemeClass.getConstructor();
        return (BlogTheme) constructor.newInstance();
    } catch (Exception e) {
        throw new RuntimeException("No such theme: " + themeName);
    }
}
  • 하지만 이처럼 Reflection을 사용하여 Dynamic Binding을 한다면, 새로운 테마가 생성되더라도 'selectTheme' 메서드의 수정 없이 테마의 이름만으로 코드를 재사용할 수 있으므로 유연하게 확장 가능한 코드를 작성할 수 있다

 

 

 

 

 

Reflection

 

 

그렇다면 리플렉션이 어떻게 동적으로 클래스의 정보를 가져오는 것인지, 또한 어떤 메서드가 존재하는지 알아보자

 

Reflection 동작 과정

  1. JVM이 처음 실행되는 과정에서, ClassLoader가 정의된 Class의 정보(필드, 메서드, 타입 등)의 바이트 코드를 읽은 후 JVM의 Metaspace에 로드한다
  2. Reflection API를 사용하기 위해, 대상이 되는 Target 클래스의 'Class' 객체를 가져온다. 'Class' 클래스에는 Target 클래스의 정보(method, field, constructor 등)가 담겨져 있다
  3. 'Class' 객체에서 다음의 메서드들을 사용하여서 정보를 가져올 수 있다
    • class, forName, newInstance, getInterfaces : Target의 Class 객체나 interface를 가져온다 
    • getMethod : Target의 메서드를 가져온다
    • getField : Target의 필드를 가져온다
    • getConstructor : Target의 생성자를 가져온다

 

Reflection Method

  • 'Class' 객체 가져오기
public static void main(String[] args) throws Exception{
    // 1. Target을 직접 사용하는 경우
    DarkMode darkMode = new DarkMode();
    Class<?> theme = DarkMode.class;
    Class<?> theme = darkMode.getClass();

    // 2. Target path만 알고 있는 경우
    Class<?> theme = Class.forName("reflection.DarkMode");
}

 

  • 'Target'의 Constructor 가져오기
public static void main(String[] args) throws Exception{

    // 1. Default Constructor를 사용하는 경우
    Constructor<?> constructor = theme.getDeclaredConstructor();

    // 2. String 파라미터를 가진 Constructor를 사용하는 경우
    Constructor<?> constructor = theme.getDeclaredConstructor(String.class);

    // 3. 모든 생성자 가져오기
    Constructor<?>[] constructors = theme.getDeclaredConstructors();

    // 4. public 생성자만 가져오기
    Constructor<?>[] constructors = theme.getConstructors();

    // 생성자로 객체 생성
    BlogTheme blogTheme = (BlogTheme) constructor.newInstance("KDH");

}

 

  • 'Target'의 Method 가져오기
public static void main(String[] args) throws Exception{

    // Class 객체 가져오기
    Class<?> theme = DarkMode.class;

    // 1. method 이름을 사용해, 파라미터가 없는 method 가져오기
    Method method = theme.getDeclaredMethod("checkTheme");

    // 2. method 이름을 사용해, 파라미터가 있는 method 가져오기
    Method method = theme.getDeclaredMethod("updateCreator", String.class);

    // 3. 모든 method 가져오기
    Method[] methods = theme.getDeclaredMethods();

    // 4. 상속받은 method와 public method 가져오기
    Method[] methods = theme.getMethods();

    // method 실행: 메서드.invoke(메서드를 호출할 객체, 메서드에 전달할 파라미터)
    BlogTheme newTheme = (BlogTheme) theme.getDeclaredConstructor().newInstance();
    method.invoke(newTheme, "DH");

    // 접근제어자 무시하고 method 실행
    method.setAccessible(true);
    method.invoke(newTheme, "KDH");

}

 

  • 'Target'의 Field값 가져오기
public static void main(String[] args) throws Exception {    
    
    // Class 객체 가져오기
    Class<?> theme = DarkMode.class;

    // 1. Class 객체에서, 접근제어자 무시하고 이름으로 field 가져오기
    Field field = theme.getDeclaredField("themeName");
    System.out.println("field = " + field.getName());

    // 2. theme, theme super 객체에서, 이름으로 public field 가져오기
    Field field = theme.getField("themeName");

    // 3. theme 객체에서, 접근제어자 무시하고 모든 field 가져오기
    Field[] fields = theme.getDeclaredFields();

    // 4. theme, theme super 객체에서, 이름으로 모든 public field 가져오기
    Field[] fields = theme.getFields();
    
}

 

  • 'Target'의 Field값 수정하기
public static void main(String[] args) throws Exception {    
    
    // Class 생성
    Class<?> theme = DarkMode.class;
    Constructor<?> constructor = theme.getDeclaredConstructor();
    BlogTheme blogTheme = (BlogTheme) constructor.newInstance();

    // Field 가져오기
    Field field = theme.getDeclaredField("themeName");

    // 1. public field인 경우
    field.set(blogTheme, "DarkMode");

    // 2. private field인 경우
    field.setAccessible(true);
    field.set(blogTheme, "DarkMode");

}

 

 

단점

  • 컴파일 시점에 타입 확인이 불가능하여 컴파일 시에 타입 확인이나 예외 검사를 할 수 없다. 즉 런타임 시에만 확인이 가능하므로 위험하다
  • 클래스, 메서드, 필드 등을 접근하여 직접 이용하기 때문에, OOP의 특징인 추상화를 위반한다
  • 불변 객체든, private field든 어떤 곳이든 접근하여 필드를 바꿀 수 있으므로 주의해야 한다
  • Reflection이 생성하는 객체에 대한 정보는 JVM에 캐시되어 있지 않기 때문에, 초기 생성시 많은 자원이 필요하다(단, 한번 캐싱된 이후로는 일반 객체와 큰 차이가 없다)
  • 런타임시 Java 보안 관리자에게 일부 권한을 부여 받는데, 이는 보안 취약점이 될 수 있다

주의해서 사용하도록 하자.. !

 

 

 

 

 

 

 

 

 

 

 

 

 

Reference

 

리플렉션: 스프링의 DI는 어떻게 동작하는걸까?

이제까지 자바와 스프링으로 개발을 해왔지만, 한번도 의존성 주입이 어떻게 이루어지는지 궁금해하지 않고 당연한 것처럼 써왔다.이번 기회를 통해, 스프링 내부 동작 방식에 대해 공부해보려

velog.io

 

[Java] 리플렉션 (Reflection)이란 무엇일까? (개념/ 예시)

서론 이번 포스팅에서 다룰 내용은 '리플렉션'이다. 최근 "리플렉션이 무엇인가요?" 라는 질문을 받았는데, 제대로 된 답변을 못한 것 같다. C# 개발을 할 때 분명 사용은 해보았지만 개념적으로

jeongkyun-it.tistory.com

 

[Java] Reflection은 무엇이고 언제/어떻게 사용하는 것이 좋을까?

구체적인 Class Type을 알지 못하더라도 해당 Class의 method, type, variable들에 접근할 수 있도록 해주는 자바 API이며,Complie time이 아닌 Runtime에 동적으로 특정 Class의 정보를 추출할 수 있는 프로그래밍

velog.io

 

대혼돈의 질서 파괴범 Reflection API에 대해 알아보자 (Java)

Reflection API는 java.lang.reflect 패키지에서 제공되는 자바 내장 API로써, 컴파일 시점이 아닌 프로그램의 실행 환경에서 객체 클래스의 정보를 읽어오고 수정할 수 있다. 이렇게 말하면 벌써 거부감

7357.tistory.com